@ssobig/writer-cli 0.2.2 → 0.3.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/README.md +19 -0
- package/asset-repository.js +7 -2
- package/config.js +2 -1
- package/package.json +1 -1
- package/templates/mystery-v1/authoring-view-preference.js +22 -5
- package/templates/mystery-v1/character-perspective-preview.js +3 -3
- package/templates/mystery-v1/codemirror6-runtime.min.js +28 -0
- package/templates/mystery-v1/component-asset-operations.js +19 -6
- package/templates/mystery-v1/component-catalog-contract.js +26 -35
- package/templates/mystery-v1/component-draft-operations.js +19 -8
- package/templates/mystery-v1/component-field-contracts.js +127 -51
- package/templates/mystery-v1/component-id-policy.js +1 -1
- package/templates/mystery-v1/component-manager.js +21 -11
- package/templates/mystery-v1/component-navigation-counts.js +9 -9
- package/templates/mystery-v1/component-registry.js +11 -5
- package/templates/mystery-v1/component-renderers.js +12 -8
- package/templates/mystery-v1/component-storage-contract.js +28 -9
- package/templates/mystery-v1/markdown-document-model.js +444 -0
- package/templates/mystery-v1/markdown-image-editor.js +320 -0
- package/templates/mystery-v1/markdown-live-editor.js +1224 -0
- package/templates/mystery-v1/page-header.js +2 -1
- package/templates/mystery-v1/timeline-model.js +235 -0
- package/tools/writer-cli/package-lock.json +2 -2
- package/tools/writer-cli/package.json +1 -1
- package/tools/writer-cli/skills/ssobig-writer-cli/SKILL.md +13 -14
- package/tools/writer-cli/skills/ssobig-writer-cli/references/assets-checkpoints.md +5 -1
- package/tools/writer-cli/skills/ssobig-writer-cli/references/errors.md +4 -1
- package/tools/writer-cli/skills/ssobig-writer-cli/references/install-auth.md +3 -3
- package/tools/writer-cli/skills/ssobig-writer-cli/references/investigation-board.md +9 -0
- package/tools/writer-cli/skills/ssobig-writer-cli/references/layout-spec.md +130 -0
- package/tools/writer-cli/skills/ssobig-writer-cli/references/projects-components.md +8 -2
- package/tools/writer-cli/skills/ssobig-writer-cli/references/read-search.md +12 -2
- package/tools/writer-cli/src/agent-service.cjs +3 -1
- package/tools/writer-cli/src/command-registry.cjs +24 -20
- package/tools/writer-cli/src/commands.cjs +106 -3
- package/tools/writer-cli/src/domain.cjs +296 -1
- package/tools/writer-cli/src/gateway.cjs +14 -0
- package/tools/writer-cli/src/mutations.cjs +167 -1
- package/tools/writer-cli/src/project-import.cjs +27 -22
|
@@ -6,6 +6,7 @@ const path = require("node:path");
|
|
|
6
6
|
const catalogContract = require("../../../templates/mystery-v1/component-catalog-contract.js");
|
|
7
7
|
const componentContract = require("../../../templates/mystery-v1/component-storage-contract.js");
|
|
8
8
|
const viewContract = require("../../../templates/mystery-v1/view-component-contract.js");
|
|
9
|
+
const timelineModel = require("../../../templates/mystery-v1/timeline-model.js");
|
|
9
10
|
const { inspectAssetFile } = require("./asset-policy.cjs");
|
|
10
11
|
const { clone, equalJson, sha256 } = require("./json.cjs");
|
|
11
12
|
const { cliError } = require("./errors.cjs");
|
|
@@ -142,6 +143,15 @@ function parseTimeline(source) {
|
|
|
142
143
|
return events;
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
// 원본 표는 사건이 시간을 소유하는 legacy 행으로 읽고, 등록된 v2 모양으로만 Component data를 만든다.
|
|
147
|
+
function timelineComponentData(events) {
|
|
148
|
+
const legacy = { events };
|
|
149
|
+
const converted = timelineModel.convertLegacyTimeline(legacy);
|
|
150
|
+
const review = timelineModel.compareLegacyToV2(legacy, converted);
|
|
151
|
+
if (!review.ok) throw cliError("E_VALIDATION", `타임라인 v2 변환이 원본과 일치하지 않습니다: ${review.mismatches.join(" / ")}`);
|
|
152
|
+
return { data: converted, review };
|
|
153
|
+
}
|
|
154
|
+
|
|
145
155
|
function characterComponent(characterSource) {
|
|
146
156
|
const characters = {};
|
|
147
157
|
const names = {};
|
|
@@ -164,13 +174,12 @@ function characterComponent(characterSource) {
|
|
|
164
174
|
tag: "플레이어 캐릭터",
|
|
165
175
|
color: CHARACTER_COLORS[characterId],
|
|
166
176
|
customSections,
|
|
167
|
-
containers: [{ id: crypto.randomUUID(), role: "primary", title: "기본 정보", sectionIds: customSections.map(section => section.id) }]
|
|
168
|
-
authorNote: ""
|
|
177
|
+
containers: [{ id: crypto.randomUUID(), role: "primary", title: "기본 정보", sectionIds: customSections.map(section => section.id) }]
|
|
169
178
|
};
|
|
170
179
|
}
|
|
171
180
|
return {
|
|
172
181
|
characters, names, order, customCharacters: [], deletedColumns: [],
|
|
173
|
-
enabledOptionalFields: ["image", "color", "tag"
|
|
182
|
+
enabledOptionalFields: ["image", "color", "tag"]
|
|
174
183
|
};
|
|
175
184
|
}
|
|
176
185
|
|
|
@@ -195,7 +204,7 @@ function progressComponent(flowSource) {
|
|
|
195
204
|
};
|
|
196
205
|
}
|
|
197
206
|
|
|
198
|
-
function cluesComponent(resourceSource
|
|
207
|
+
function cluesComponent(resourceSource) {
|
|
199
208
|
if (!isObject(resourceSource)) throw cliError("E_VALIDATION", "단서 resource 원본이 객체가 아닙니다.");
|
|
200
209
|
const clues = [];
|
|
201
210
|
Object.entries(resourceSource).forEach(([sourceId, item]) => {
|
|
@@ -207,7 +216,6 @@ function cluesComponent(resourceSource, writerNote) {
|
|
|
207
216
|
title: name,
|
|
208
217
|
location: source.verification === undefined ? "1차 조사" : "2차 조사",
|
|
209
218
|
description: text(source.content),
|
|
210
|
-
secret: text(source.verification),
|
|
211
219
|
tags: [source.verification === undefined ? "자료" : "제보"],
|
|
212
220
|
color: source.verification === undefined ? "#497fa8" : "#a86b49",
|
|
213
221
|
confirmed: true,
|
|
@@ -216,8 +224,7 @@ function cluesComponent(resourceSource, writerNote) {
|
|
|
216
224
|
});
|
|
217
225
|
return {
|
|
218
226
|
clues,
|
|
219
|
-
enabledOptionalFields: ["confirmed", "image", "location", "tags", "color"
|
|
220
|
-
writerNote
|
|
227
|
+
enabledOptionalFields: ["confirmed", "image", "location", "tags", "color"]
|
|
221
228
|
};
|
|
222
229
|
}
|
|
223
230
|
|
|
@@ -357,6 +364,7 @@ function loadTrueWriterCase(input, options = {}) {
|
|
|
357
364
|
const progress = progressComponent(jsonData.game_set.flow);
|
|
358
365
|
const ending = endingComponent(jsonData.game_set.ending);
|
|
359
366
|
const timelineEvents = parseTimeline(docs["03-timeline.md"]);
|
|
367
|
+
const timeline = timelineComponentData(timelineEvents);
|
|
360
368
|
const resources = Object.keys(jsonData.resource).length;
|
|
361
369
|
const components = normalizeImportComponents([
|
|
362
370
|
component("basic", {
|
|
@@ -372,25 +380,19 @@ function loadTrueWriterCase(input, options = {}) {
|
|
|
372
380
|
}),
|
|
373
381
|
component("progress", progress),
|
|
374
382
|
component("common", {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
deletedBaseSections: []
|
|
383
|
+
documents: [
|
|
384
|
+
{ id: crypto.randomUUID(), title: "작품 개요", body: docs["00-overview.md"], subdocuments: [] },
|
|
385
|
+
{ id: crypto.randomUUID(), title: "프롤로그", body: text(jsonData.game_set.flow?.["00 - 프롤로그"]?.script), subdocuments: [] },
|
|
386
|
+
{ id: crypto.randomUUID(), title: "사건의 전말", body: "디자이너 전용 정답 및 사건의 전말", subdocuments: [{ id: crypto.randomUUID(), title: "사건의 진실", body: docs["01-truth.md"] }] },
|
|
387
|
+
{ id: crypto.randomUUID(), title: "에필로그", body: "게임 종료 후 공개 문서", subdocuments: [{ id: crypto.randomUUID(), title: "에필로그", body: text(jsonData.game_set.flow?.["14 - 에필로그"]?.script) }] }
|
|
388
|
+
],
|
|
389
|
+
deletedDocumentIds: []
|
|
383
390
|
}),
|
|
384
391
|
component("characters", characterComponent(jsonData.game_set.character)),
|
|
385
392
|
component("assets", { assets: {} }),
|
|
386
|
-
component("clues", cluesComponent(jsonData.resource
|
|
387
|
-
component("timeline",
|
|
393
|
+
component("clues", cluesComponent(jsonData.resource)),
|
|
394
|
+
component("timeline", timeline.data),
|
|
388
395
|
component("ending", ending),
|
|
389
|
-
component("postgame", {
|
|
390
|
-
truth: [{ id: crypto.randomUUID(), title: "사건의 진실", body: docs["01-truth.md"] }],
|
|
391
|
-
epilogue: [{ id: crypto.randomUUID(), title: "에필로그", body: text(jsonData.game_set.flow?.["14 - 에필로그"]?.script) }],
|
|
392
|
-
intro: { truth: "디자이너 전용 정답 및 사건의 전말", epilogue: "게임 종료 후 공개 문서" }
|
|
393
|
-
}),
|
|
394
396
|
component("author-notes", {
|
|
395
397
|
notes: [
|
|
396
398
|
["character-design", "캐릭터 설계 원문", docs["02-characters.md"]],
|
|
@@ -426,6 +428,9 @@ function loadTrueWriterCase(input, options = {}) {
|
|
|
426
428
|
endingGroups: Object.keys(jsonData.game_set.ending || {}).length,
|
|
427
429
|
endingOutcomes: ending.outcomes.length,
|
|
428
430
|
timelineEvents: timelineEvents.length,
|
|
431
|
+
timelineLevels: timeline.data.timeLevels.length,
|
|
432
|
+
timelineEntries: timeline.data.entries.length,
|
|
433
|
+
timelineConversion: { shape: "v2", lossless: timeline.review.ok, ...timeline.review.counts },
|
|
429
434
|
components: components.length,
|
|
430
435
|
assets: assetFiles.length,
|
|
431
436
|
unexplained: 0
|