@hanna84/mcp-writing 1.5.1 → 1.5.2
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 +10 -0
- package/importer.js +78 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [v1.5.2](https://github.com/hannasdev/mcp-writing.git
|
|
8
|
+
/compare/v1.5.1...v1.5.2)
|
|
9
|
+
|
|
10
|
+
- fix: avoid nested scenes/projects paths on import [`#44`](https://github.com/hannasdev/mcp-writing.git
|
|
11
|
+
/pull/44)
|
|
12
|
+
|
|
7
13
|
#### [v1.5.1](https://github.com/hannasdev/mcp-writing.git
|
|
8
14
|
/compare/v1.5.0...v1.5.1)
|
|
9
15
|
|
|
16
|
+
> 19 April 2026
|
|
17
|
+
|
|
10
18
|
- chore: switch license from MIT to AGPL v3 [`#43`](https://github.com/hannasdev/mcp-writing.git
|
|
11
19
|
/pull/43)
|
|
20
|
+
- Release 1.5.1 [`73b233f`](https://github.com/hannasdev/mcp-writing.git
|
|
21
|
+
/commit/73b233f2f210d435386f1235aa15ebc3c945542b)
|
|
12
22
|
|
|
13
23
|
#### [v1.5.0](https://github.com/hannasdev/mcp-writing.git
|
|
14
24
|
/compare/v1.4.8...v1.5.0)
|
package/importer.js
CHANGED
|
@@ -129,6 +129,57 @@ function removeIfExists(filePath) {
|
|
|
129
129
|
if (filePath && fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
function resolveSyncRootFromPrefix(prefix, syncDirAbs) {
|
|
133
|
+
const parsedRoot = path.parse(syncDirAbs).root;
|
|
134
|
+
|
|
135
|
+
if (!prefix) {
|
|
136
|
+
return parsedRoot ? path.resolve(parsedRoot) : path.resolve(syncDirAbs);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// On Windows, a regex prefix like "C:" would resolve relative to cwd on drive C.
|
|
140
|
+
// Use the true drive root instead (e.g., "C:\\").
|
|
141
|
+
if (/^[a-zA-Z]:$/.test(prefix)) {
|
|
142
|
+
return parsedRoot || `${prefix}${path.sep}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return path.resolve(prefix);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function detectScopedSyncDir(syncDirAbs) {
|
|
149
|
+
const normalized = syncDirAbs.split(path.sep).join("/");
|
|
150
|
+
|
|
151
|
+
const universeMatch = normalized.match(/^(.*)\/universes\/([^/]+)\/([^/]+)(?:\/scenes)?$/);
|
|
152
|
+
if (universeMatch) {
|
|
153
|
+
const prefix = universeMatch[1];
|
|
154
|
+
const universeId = universeMatch[2];
|
|
155
|
+
const projectSlug = universeMatch[3];
|
|
156
|
+
const syncRoot = resolveSyncRootFromPrefix(prefix, syncDirAbs);
|
|
157
|
+
const projectRoot = path.join(syncRoot, "universes", universeId, projectSlug);
|
|
158
|
+
return {
|
|
159
|
+
projectId: `${universeId}/${projectSlug}`,
|
|
160
|
+
scope: "universe",
|
|
161
|
+
syncRoot,
|
|
162
|
+
projectRoot,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const projectMatch = normalized.match(/^(.*)\/projects\/([^/]+)(?:\/scenes)?$/);
|
|
167
|
+
if (projectMatch) {
|
|
168
|
+
const prefix = projectMatch[1];
|
|
169
|
+
const projectSlug = projectMatch[2];
|
|
170
|
+
const syncRoot = resolveSyncRootFromPrefix(prefix, syncDirAbs);
|
|
171
|
+
const projectRoot = path.join(syncRoot, "projects", projectSlug);
|
|
172
|
+
return {
|
|
173
|
+
projectId: projectSlug,
|
|
174
|
+
scope: "project",
|
|
175
|
+
syncRoot,
|
|
176
|
+
projectRoot,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
132
183
|
export function importScrivenerSync({
|
|
133
184
|
scrivenerDir,
|
|
134
185
|
mcpSyncDir,
|
|
@@ -138,31 +189,48 @@ export function importScrivenerSync({
|
|
|
138
189
|
}) {
|
|
139
190
|
const scrivenerDirAbs = path.resolve(scrivenerDir);
|
|
140
191
|
const mcpSyncDirAbs = path.resolve(mcpSyncDir);
|
|
192
|
+
const scopedSyncDir = detectScopedSyncDir(mcpSyncDirAbs);
|
|
193
|
+
const fallbackProjectId = path.basename(mcpSyncDirAbs).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
141
194
|
const resolvedProjectId = projectId
|
|
142
195
|
? projectId
|
|
143
|
-
:
|
|
196
|
+
: scopedSyncDir?.projectId ?? fallbackProjectId;
|
|
144
197
|
|
|
145
198
|
const projectIdCheck = validateProjectId(resolvedProjectId);
|
|
146
199
|
if (!projectIdCheck.ok) {
|
|
147
200
|
throw new Error(`Invalid project_id '${resolvedProjectId}': ${projectIdCheck.reason}`);
|
|
148
201
|
}
|
|
149
202
|
|
|
203
|
+
if (scopedSyncDir && projectId && projectId !== scopedSyncDir.projectId) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`project_id '${projectId}' does not match WRITING_SYNC_DIR scope '${scopedSyncDir.projectId}'. `
|
|
206
|
+
+ "Set WRITING_SYNC_DIR to the sync root or use the matching project_id."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
150
210
|
if (!fs.existsSync(scrivenerDirAbs)) {
|
|
151
211
|
throw new Error(`Scrivener sync dir not found: ${scrivenerDirAbs}`);
|
|
152
212
|
}
|
|
153
213
|
|
|
154
|
-
// Route universe/project IDs to universes/<universe>/<project>/scenes,
|
|
155
|
-
// matching the convention used by inferProjectAndUniverse in sync.js.
|
|
156
|
-
const segments = resolvedProjectId.split("/");
|
|
157
214
|
let scenesDir;
|
|
158
215
|
let scenesBoundaryRoot;
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
216
|
+
if (scopedSyncDir) {
|
|
217
|
+
scenesBoundaryRoot = path.join(
|
|
218
|
+
scopedSyncDir.syncRoot,
|
|
219
|
+
scopedSyncDir.scope === "universe" ? "universes" : "projects"
|
|
220
|
+
);
|
|
221
|
+
scenesDir = path.join(scopedSyncDir.projectRoot, "scenes");
|
|
163
222
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
223
|
+
// Route universe/project IDs to universes/<universe>/<project>/scenes,
|
|
224
|
+
// matching the convention used by inferProjectAndUniverse in sync.js.
|
|
225
|
+
const segments = resolvedProjectId.split("/");
|
|
226
|
+
if (segments.length === 2) {
|
|
227
|
+
const [universeId, projectSlug] = segments;
|
|
228
|
+
scenesBoundaryRoot = path.join(mcpSyncDirAbs, "universes");
|
|
229
|
+
scenesDir = path.resolve(scenesBoundaryRoot, universeId, projectSlug, "scenes");
|
|
230
|
+
} else {
|
|
231
|
+
scenesBoundaryRoot = path.join(mcpSyncDirAbs, "projects");
|
|
232
|
+
scenesDir = path.resolve(scenesBoundaryRoot, resolvedProjectId, "scenes");
|
|
233
|
+
}
|
|
166
234
|
}
|
|
167
235
|
|
|
168
236
|
const relFromBoundary = path.relative(scenesBoundaryRoot, scenesDir);
|