@mnemonik/cursor-hooks 0.7.5 → 0.7.7
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/dist/hook.js +167 -64
- package/dist/hook.js.map +1 -1
- package/dist/install.d.ts +5 -9
- package/dist/install.js +194 -83
- package/dist/install.js.map +1 -1
- package/dist/lib/behavior.d.ts +4 -2
- package/dist/lib/behavior.js +4 -2
- package/dist/lib/behavior.js.map +1 -1
- package/dist/lib/contextOverflow.js +90 -7
- package/dist/lib/contextOverflow.js.map +1 -1
- package/dist/lib/cursorOutput.js +4 -2
- package/dist/lib/cursorOutput.js.map +1 -1
- package/dist/lib/dedupGuard.d.ts +1 -0
- package/dist/lib/dedupGuard.js +4 -1
- package/dist/lib/dedupGuard.js.map +1 -1
- package/dist/lib/http.d.ts +10 -5
- package/dist/lib/http.js +40 -26
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/installerSupport.d.ts +33 -0
- package/dist/lib/installerSupport.js +563 -0
- package/dist/lib/installerSupport.js.map +1 -0
- package/dist/lib/mcpOwnership.d.ts +19 -0
- package/dist/lib/mcpOwnership.js +127 -0
- package/dist/lib/mcpOwnership.js.map +1 -0
- package/dist/lib/retiredCursorRule.d.ts +9 -0
- package/dist/lib/retiredCursorRule.js +87 -0
- package/dist/lib/retiredCursorRule.js.map +1 -0
- package/dist/lib/types.d.ts +12 -11
- package/dist/lib/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/lib/files.d.ts +0 -20
- package/dist/lib/files.js +0 -52
- package/dist/lib/files.js.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const BARE_MNEMONIK_TOOLS = new Set(['memory_tools', 'memory_discover', 'session_bootstrap']);
|
|
5
|
+
const CURSOR_NAMESPACED_TOOL = /^(?:MCP:)?(.+)\.(memory_tools|memory_discover|session_bootstrap)$/;
|
|
6
|
+
const CLAUDE_STYLE_NAMESPACED_TOOL = /^mcp__(.+)__(memory_tools|memory_discover|session_bootstrap)$/;
|
|
7
|
+
export function isCursorMcpToolEvent(tool) {
|
|
8
|
+
return tool.startsWith('MCP:') || tool.startsWith('mcp__') || BARE_MNEMONIK_TOOLS.has(tool);
|
|
9
|
+
}
|
|
10
|
+
/** Canonical server-facing name; call only after ownership has been proved. */
|
|
11
|
+
export function canonicalMnemonikMcpTool(tool) {
|
|
12
|
+
if (BARE_MNEMONIK_TOOLS.has(tool))
|
|
13
|
+
return tool;
|
|
14
|
+
const match = CURSOR_NAMESPACED_TOOL.exec(tool) ?? CLAUDE_STYLE_NAMESPACED_TOOL.exec(tool);
|
|
15
|
+
return match?.[2] ?? null;
|
|
16
|
+
}
|
|
17
|
+
function normalizeUrl(raw) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(raw);
|
|
20
|
+
url.hash = '';
|
|
21
|
+
if (url.pathname.length > 1)
|
|
22
|
+
url.pathname = url.pathname.replace(/\/+$/, '');
|
|
23
|
+
return url.toString();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function expectedMcpUrl(hookServer) {
|
|
30
|
+
return normalizeUrl(`${hookServer.replace(/\/+$/, '')}/mcp`);
|
|
31
|
+
}
|
|
32
|
+
function namespacedServerKey(tool) {
|
|
33
|
+
const match = CURSOR_NAMESPACED_TOOL.exec(tool) ?? CLAUDE_STYLE_NAMESPACED_TOOL.exec(tool);
|
|
34
|
+
return match?.[1]?.trim() || null;
|
|
35
|
+
}
|
|
36
|
+
function definitionTargetsMnemonik(definition, expectedUrl) {
|
|
37
|
+
for (const candidate of [definition.url, definition.serverUrl]) {
|
|
38
|
+
if (typeof candidate === 'string' && normalizeUrl(candidate) === expectedUrl)
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Older Cursor installations sometimes use mcp-remote over stdio. Accept
|
|
42
|
+
// that only when the exact expected endpoint is an argument to the known
|
|
43
|
+
// wrapper; a generic command or a coincidental bare tool name proves nothing.
|
|
44
|
+
if (typeof definition.command === 'string' &&
|
|
45
|
+
/^(?:npx(?:\.cmd)?|bunx|pnpx)$/.test(definition.command) &&
|
|
46
|
+
Array.isArray(definition.args) &&
|
|
47
|
+
definition.args.some((arg) => typeof arg === 'string' && /(?:^|\/)mcp-remote(?:@[^/]*)?$/.test(arg)) &&
|
|
48
|
+
definition.args.some((arg) => typeof arg === 'string' && normalizeUrl(arg) === expectedUrl)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
async function readServerBinding(path, serverKey) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(await readFile(path, 'utf8'));
|
|
56
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
57
|
+
return undefined;
|
|
58
|
+
const mcpServers = parsed.mcpServers;
|
|
59
|
+
if (!mcpServers || typeof mcpServers !== 'object' || Array.isArray(mcpServers)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
if (!Object.prototype.hasOwnProperty.call(mcpServers, serverKey))
|
|
63
|
+
return undefined;
|
|
64
|
+
const definition = mcpServers[serverKey];
|
|
65
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
return definition;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function configuredServerKeyTargetsMnemonik(input, serverKey, expectedUrl) {
|
|
75
|
+
const serverKeys = new Set([serverKey]);
|
|
76
|
+
if (serverKey.startsWith('user-') && serverKey.length > 'user-'.length) {
|
|
77
|
+
// Older Cursor builds exposed their internal `user-<mcp.json key>` id in
|
|
78
|
+
// the command field. Current builds expose the config key itself.
|
|
79
|
+
serverKeys.add(serverKey.slice('user-'.length));
|
|
80
|
+
}
|
|
81
|
+
const roots = new Set([input.cwd, ...(input.workspaceRoots ?? [])]);
|
|
82
|
+
const paths = [...roots].map((root) => join(root, '.cursor', 'mcp.json'));
|
|
83
|
+
paths.push(join(homedir(), '.cursor', 'mcp.json'));
|
|
84
|
+
let found = false;
|
|
85
|
+
for (const path of new Set(paths)) {
|
|
86
|
+
for (const candidateKey of serverKeys) {
|
|
87
|
+
const definition = await readServerBinding(path, candidateKey);
|
|
88
|
+
if (definition === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
found = true;
|
|
91
|
+
// Ambiguous project/user bindings fail closed. Cursor's precedence and
|
|
92
|
+
// payload have regressed more than once; a conflicting shadow key must not
|
|
93
|
+
// authorize forwarding another server's arguments or result.
|
|
94
|
+
if (!definitionTargetsMnemonik(definition, expectedUrl))
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Prove that a globally observed Cursor MCP event belongs to Mnemonik.
|
|
102
|
+
* A namespace is only a user-configurable server key, not authentication.
|
|
103
|
+
* Without exact event URL/command metadata, that extracted key must be bound
|
|
104
|
+
* to the configured Mnemonik endpoint in Cursor's MCP config. Missing,
|
|
105
|
+
* unreadable, or conflicting bindings fail closed.
|
|
106
|
+
*/
|
|
107
|
+
export async function isMnemonikMcpCall(input) {
|
|
108
|
+
const serverKey = namespacedServerKey(input.tool);
|
|
109
|
+
if (!serverKey && !BARE_MNEMONIK_TOOLS.has(input.tool))
|
|
110
|
+
return false;
|
|
111
|
+
const expectedUrl = expectedMcpUrl(input.hookServer);
|
|
112
|
+
if (!expectedUrl)
|
|
113
|
+
return false;
|
|
114
|
+
// Cursor namespaces tools from a user-configurable server key, not an
|
|
115
|
+
// authenticated identity. When the event supplies stronger server metadata,
|
|
116
|
+
// it must agree even for a namespaced tool. Namespace is a compatibility
|
|
117
|
+
// fallback only for legacy payloads that carry neither field.
|
|
118
|
+
if (input.url !== undefined)
|
|
119
|
+
return normalizeUrl(input.url) === expectedUrl;
|
|
120
|
+
if (input.command) {
|
|
121
|
+
if (normalizeUrl(input.command) === expectedUrl)
|
|
122
|
+
return true;
|
|
123
|
+
return configuredServerKeyTargetsMnemonik(input, input.command, expectedUrl);
|
|
124
|
+
}
|
|
125
|
+
return serverKey ? configuredServerKeyTargetsMnemonik(input, serverKey, expectedUrl) : false;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=mcpOwnership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpOwnership.js","sourceRoot":"","sources":["../../src/lib/mcpOwnership.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const RETIRED_CURSOR_RULE_PATH: string;
|
|
2
|
+
export type RetiredRuleMigration = 'absent' | 'preserved' | 'removed' | 'failed';
|
|
3
|
+
export declare function isRetiredMnemonikManagedRule(content: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Remove only an exact historical server-generated Cursor rule. Modified and
|
|
6
|
+
* user-authored files are preserved; unreadable/unremovable files report a
|
|
7
|
+
* distinct failure so the explicit installer cannot claim a clean migration.
|
|
8
|
+
*/
|
|
9
|
+
export declare function migrateRetiredCursorRule(cwd: string): Promise<RetiredRuleMigration>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { atomicRemoveText } from './installerSupport.js';
|
|
4
|
+
export const RETIRED_CURSOR_RULE_PATH = join('.cursor', 'rules', 'memory_tools.mdc');
|
|
5
|
+
function historicalRuleSuffix({ mcpServer, codeSearch, unlockTool, }) {
|
|
6
|
+
return [
|
|
7
|
+
...(mcpServer
|
|
8
|
+
? [
|
|
9
|
+
'mcpServer: user-mnemonik',
|
|
10
|
+
'',
|
|
11
|
+
'Pass cwd verbatim in every call; never search for it.',
|
|
12
|
+
'CallMcpTool server is mcpServer (Cursor prefixed internal id), not the mcp.json key — e.g. user-mnemonik not mnemonik.',
|
|
13
|
+
`Tier-1 toolNames on that server: memory_tools, memory_discover, session_bootstrap${unlockTool ? ', __unlock_memory_tools__' : ''}.`,
|
|
14
|
+
]
|
|
15
|
+
: ['', 'Pass cwd verbatim in every call; never search for it.']),
|
|
16
|
+
'memory_search, file_context, checkpoint run INSIDE memory_tools — async () => mnemonik.X({...}) — never as top-level tools (a direct call fails).',
|
|
17
|
+
"To use memory_tools, or any mnemonik.* method you don't already know: call memory_discover first for the exact method, schema, and runnable example.",
|
|
18
|
+
'',
|
|
19
|
+
unlockTool
|
|
20
|
+
? '- Start: __unlock_memory_tools__, then session_bootstrap({cwd}) once — skip if context already has a "Mnemonik project context (cached" block.'
|
|
21
|
+
: '- Start: session_bootstrap({cwd}) once — skip if context already has a "Mnemonik project context (cached" block.',
|
|
22
|
+
'- Before non-trivially editing a file: mnemonik.file_context({cwd, filePaths:[...]}).',
|
|
23
|
+
"- After meaningful changes / before saying you're done: mnemonik.checkpoint({cwd, summary}).",
|
|
24
|
+
'- Unsure of a project fact or past decision: mnemonik.memory_search({cwd, query}).',
|
|
25
|
+
...(codeSearch
|
|
26
|
+
? [
|
|
27
|
+
"- For where-is-X code queries, mnemonik.code_search({cwd, query}) adds what Cursor's built-in search cannot: forbidden/disputed-pattern warnings, drift flags, and cross-session project memory — use it when correctness or project history matters, not just location.",
|
|
28
|
+
]
|
|
29
|
+
: []),
|
|
30
|
+
'',
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
33
|
+
// These are the four byte-exact static suffixes emitted by the retired server
|
|
34
|
+
// renderer across commits 4e1d033e, ca2d1c72, 89a5c4ff, and 3d57f946. The
|
|
35
|
+
// project name/id/cwd header fields were dynamic; every other byte must match a
|
|
36
|
+
// known renderer version or the file is treated as user-authored and preserved.
|
|
37
|
+
const HISTORICAL_RULE_SUFFIXES = new Set([
|
|
38
|
+
historicalRuleSuffix({ mcpServer: false, codeSearch: false, unlockTool: true }),
|
|
39
|
+
historicalRuleSuffix({ mcpServer: true, codeSearch: false, unlockTool: true }),
|
|
40
|
+
historicalRuleSuffix({ mcpServer: true, codeSearch: true, unlockTool: true }),
|
|
41
|
+
historicalRuleSuffix({ mcpServer: true, codeSearch: true, unlockTool: false }),
|
|
42
|
+
]);
|
|
43
|
+
function hasGeneratedField(line, prefix) {
|
|
44
|
+
return line?.startsWith(prefix) === true && line.length > prefix.length;
|
|
45
|
+
}
|
|
46
|
+
export function isRetiredMnemonikManagedRule(content) {
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
if (lines[0] !== '---' ||
|
|
49
|
+
lines[1] !== 'alwaysApply: true' ||
|
|
50
|
+
lines[2] !== '---' ||
|
|
51
|
+
lines[3] !== '# Mnemonik Memory Tools' ||
|
|
52
|
+
!hasGeneratedField(lines[4], 'project: ') ||
|
|
53
|
+
!hasGeneratedField(lines[5], 'projectId: ') ||
|
|
54
|
+
!hasGeneratedField(lines[6], 'cwd: ')) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return HISTORICAL_RULE_SUFFIXES.has(lines.slice(7).join('\n'));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove only an exact historical server-generated Cursor rule. Modified and
|
|
61
|
+
* user-authored files are preserved; unreadable/unremovable files report a
|
|
62
|
+
* distinct failure so the explicit installer cannot claim a clean migration.
|
|
63
|
+
*/
|
|
64
|
+
export async function migrateRetiredCursorRule(cwd) {
|
|
65
|
+
const path = join(cwd, RETIRED_CURSOR_RULE_PATH);
|
|
66
|
+
let content;
|
|
67
|
+
try {
|
|
68
|
+
content = await readFile(path, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT')
|
|
72
|
+
return 'absent';
|
|
73
|
+
return 'failed';
|
|
74
|
+
}
|
|
75
|
+
if (!isRetiredMnemonikManagedRule(content))
|
|
76
|
+
return 'preserved';
|
|
77
|
+
try {
|
|
78
|
+
await atomicRemoveText(path, content);
|
|
79
|
+
return 'removed';
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT')
|
|
83
|
+
return 'absent';
|
|
84
|
+
return 'failed';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=retiredCursorRule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retiredCursorRule.js","sourceRoot":"","sources":["../../src/lib/retiredCursorRule.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -22,13 +22,24 @@ export interface CursorHookInput {
|
|
|
22
22
|
messages_to_compact?: number;
|
|
23
23
|
is_first_compaction?: boolean;
|
|
24
24
|
tool_name?: string;
|
|
25
|
-
|
|
25
|
+
tool_use_id?: string;
|
|
26
|
+
/** Object for tool hooks; JSON string for Cursor's documented MCP hook payloads. */
|
|
27
|
+
tool_input?: Record<string, unknown> | string;
|
|
26
28
|
tool_output?: unknown;
|
|
27
29
|
command?: string;
|
|
30
|
+
/** Remote MCP endpoint on Cursor versions that populate the documented field. */
|
|
31
|
+
url?: string;
|
|
28
32
|
mcp_tool?: string;
|
|
29
33
|
mcp_args?: Record<string, unknown>;
|
|
30
34
|
mcp_result?: unknown;
|
|
35
|
+
/** JSON-encoded MCP result in Cursor's documented afterMCPExecution payload. */
|
|
36
|
+
result_json?: string;
|
|
31
37
|
file_path?: string;
|
|
38
|
+
/** afterFileEdit only. That event deliberately has no tool_use_id. */
|
|
39
|
+
edits?: Array<{
|
|
40
|
+
old_string?: string;
|
|
41
|
+
new_string?: string;
|
|
42
|
+
}>;
|
|
32
43
|
followup_count?: number;
|
|
33
44
|
/** Stop-loop counter Cursor actually sends (docs say followup_count; real payloads carry loop_count). */
|
|
34
45
|
loop_count?: number;
|
|
@@ -162,16 +173,6 @@ export interface InjectionsResponse {
|
|
|
162
173
|
* MNEMONIK_CURSOR_INJECTION_CHANNEL so a rollback needs no republish.
|
|
163
174
|
*/
|
|
164
175
|
deliveryChannel?: 'legacy' | 'agent_message';
|
|
165
|
-
/**
|
|
166
|
-
* Files the server asks the hook to write to the workspace, idempotently
|
|
167
|
-
* (skip-if-unchanged). Currently only the Cursor memory-tools rule file on
|
|
168
|
-
* SessionStart. Independent of `injections` — present even when `injections`
|
|
169
|
-
* is empty, which is the common Cursor case.
|
|
170
|
-
*/
|
|
171
|
-
filesToWrite?: Array<{
|
|
172
|
-
path: string;
|
|
173
|
-
content: string;
|
|
174
|
-
}>;
|
|
175
176
|
}
|
|
176
177
|
/**
|
|
177
178
|
* Server response from /api/v1/hooks/jit — JIT Knowledge Injector gate
|
package/dist/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAyNA,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnemonik/cursor-hooks",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "Cursor IDE hooks for Mnemonik memory awareness and host-side grounding gates. Hooks only: does not install skills, rules, AGENTS.md, .mnemonik.json, or MCP config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/dist/lib/files.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface ProjectFile {
|
|
2
|
-
path: string;
|
|
3
|
-
content: string;
|
|
4
|
-
}
|
|
5
|
-
export interface WriteProjectFilesResult {
|
|
6
|
-
written: string[];
|
|
7
|
-
skipped: string[];
|
|
8
|
-
rejected: string[];
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Write server-provided files into the workspace idempotently.
|
|
12
|
-
*
|
|
13
|
-
* For each entry: resolve under `cwd`, refuse anything that escapes `cwd`
|
|
14
|
-
* (absolute path or a `..` traversal), skip the write when the on-disk content
|
|
15
|
-
* already matches byte-for-byte (no churn, no mtime bump — keeps git clean
|
|
16
|
-
* across sessions), else create parent dirs and write. Fail-open per file: a
|
|
17
|
-
* single bad/locked path never aborts the others, and the caller wraps the whole
|
|
18
|
-
* call so a write error never breaks the hook session.
|
|
19
|
-
*/
|
|
20
|
-
export declare function writeProjectFiles(cwd: string, files: ProjectFile[]): Promise<WriteProjectFilesResult>;
|
package/dist/lib/files.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
-
/**
|
|
4
|
-
* Write server-provided files into the workspace idempotently.
|
|
5
|
-
*
|
|
6
|
-
* For each entry: resolve under `cwd`, refuse anything that escapes `cwd`
|
|
7
|
-
* (absolute path or a `..` traversal), skip the write when the on-disk content
|
|
8
|
-
* already matches byte-for-byte (no churn, no mtime bump — keeps git clean
|
|
9
|
-
* across sessions), else create parent dirs and write. Fail-open per file: a
|
|
10
|
-
* single bad/locked path never aborts the others, and the caller wraps the whole
|
|
11
|
-
* call so a write error never breaks the hook session.
|
|
12
|
-
*/
|
|
13
|
-
export async function writeProjectFiles(cwd, files) {
|
|
14
|
-
const result = { written: [], skipped: [], rejected: [] };
|
|
15
|
-
for (const file of files) {
|
|
16
|
-
const relPath = file.path;
|
|
17
|
-
// Reject absolute or empty paths outright — the server only ever asks for
|
|
18
|
-
// workspace-relative paths.
|
|
19
|
-
if (!relPath || isAbsolute(relPath)) {
|
|
20
|
-
result.rejected.push(relPath);
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const target = resolve(cwd, relPath);
|
|
24
|
-
const rel = relative(cwd, target);
|
|
25
|
-
// Refuse anything that resolves to cwd itself or climbs out of it.
|
|
26
|
-
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
|
|
27
|
-
result.rejected.push(relPath);
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
let existing = null;
|
|
32
|
-
try {
|
|
33
|
-
existing = await readFile(target, 'utf8');
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
existing = null; // missing/unreadable → (over)write
|
|
37
|
-
}
|
|
38
|
-
if (existing === file.content) {
|
|
39
|
-
result.skipped.push(relPath);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
await mkdir(dirname(target), { recursive: true });
|
|
43
|
-
await writeFile(target, file.content, 'utf8');
|
|
44
|
-
result.written.push(relPath);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
result.rejected.push(relPath);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
//# sourceMappingURL=files.js.map
|
package/dist/lib/files.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/lib/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAanE;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAW,EACX,KAAoB;IAEpB,MAAM,MAAM,GAA4B,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAEnF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1B,0EAA0E;QAC1E,4BAA4B;QAC5B,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAClC,mEAAmE;QACnE,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1D,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,IAAI,QAAQ,GAAkB,IAAI,CAAC;YACnC,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,GAAG,IAAI,CAAC,CAAC,mCAAmC;YACtD,CAAC;YACD,IAAI,QAAQ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC9B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC7B,SAAS;YACX,CAAC;YACD,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC9C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|