stego-cli 0.1.5 → 0.1.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/.gitignore +1 -0
- package/.markdownlint.manuscript.json +8 -0
- package/README.md +39 -189
- package/dist/stego-cli.js +163 -24
- package/package.json +7 -5
- package/projects/fiction-example/.markdownlint.json +5 -0
- package/projects/fiction-example/.markdownlint.manuscript.json +8 -0
- package/projects/{plague-demo → fiction-example}/README.md +5 -5
- package/projects/{plague-demo → fiction-example}/manuscript/300-the-hearing.md +1 -13
- package/projects/{plague-demo → fiction-example}/package.json +1 -1
- package/projects/{plague-demo → fiction-example}/spine/characters.md +4 -0
- package/projects/{plague-demo → fiction-example}/spine/locations.md +4 -0
- package/projects/{plague-demo → fiction-example}/spine/sources.md +3 -0
- package/projects/{plague-demo → fiction-example}/stego-project.json +1 -1
- package/projects/stego-docs/README.md +27 -0
- package/projects/stego-docs/manuscript/100-what-stego-is.md +56 -0
- package/projects/stego-docs/manuscript/200-install-and-initialize.md +68 -0
- package/projects/stego-docs/manuscript/300-workspace-layout-and-vscode.md +56 -0
- package/projects/stego-docs/manuscript/400-everyday-workflow-and-commands.md +70 -0
- package/projects/stego-docs/manuscript/500-project-configuration.md +58 -0
- package/projects/stego-docs/manuscript/600-spine-and-browser-workflows.md +55 -0
- package/projects/stego-docs/manuscript/700-validation-and-stage-gates.md +53 -0
- package/projects/stego-docs/manuscript/800-build-export-and-release-outputs.md +53 -0
- package/projects/{docs-demo → stego-docs}/package.json +1 -1
- package/projects/stego-docs/spine/commands.md +62 -0
- package/projects/stego-docs/spine/concepts.md +72 -0
- package/projects/stego-docs/spine/configuration.md +57 -0
- package/projects/stego-docs/spine/integrations.md +43 -0
- package/projects/stego-docs/spine/workflows.md +48 -0
- package/projects/stego-docs/stego-project.json +50 -0
- package/docs/conventions.md +0 -182
- package/docs/workflow.md +0 -78
- package/projects/docs-demo/README.md +0 -20
- package/projects/docs-demo/manuscript/100-what-stego-is.md +0 -37
- package/projects/docs-demo/manuscript/200-writing-workflow.md +0 -69
- package/projects/docs-demo/manuscript/300-quality-gates.md +0 -36
- package/projects/docs-demo/manuscript/400-build-and-export.md +0 -42
- package/projects/docs-demo/stego-project.json +0 -9
- package/projects/plague-demo/.markdownlint.json +0 -4
- /package/projects/{docs-demo → fiction-example}/dist/.gitkeep +0 -0
- /package/projects/{plague-demo → fiction-example}/manuscript/100-the-commission.md +0 -0
- /package/projects/{plague-demo → fiction-example}/manuscript/200-at-the-wards.md +0 -0
- /package/projects/{plague-demo → fiction-example}/manuscript/400-the-final-account.md +0 -0
- /package/projects/{plague-demo → fiction-example}/notes/style-guide.md +0 -0
- /package/projects/{plague-demo → stego-docs}/dist/.gitkeep +0 -0
- /package/projects/{docs-demo → stego-docs}/notes/implementation-notes.md +0 -0
- /package/projects/{docs-demo → stego-docs}/notes/style-guide.md +0 -0
package/.gitignore
CHANGED
package/README.md
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
# Stego
|
|
1
|
+
# Stego CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`stego-cli` is an installable CLI for the Stego writing workflow.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It scaffolds a Stego workspace, validates manuscript structure and metadata, runs stage-aware quality gates, builds compiled markdown outputs, and exports release formats.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This repository is the source for the CLI and the template/example projects that `stego init` scaffolds.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Project-per-folder structure inside one monorepo.
|
|
11
|
-
- Flexible manuscript files with per-project metadata requirements.
|
|
12
|
-
- Configurable manuscript grouping via `compileStructure.levels` (for example `part` + `chapter`).
|
|
13
|
-
- Project-defined spine categories (configured in each `stego-project.json`).
|
|
14
|
-
- Deterministic build into one manuscript Markdown file.
|
|
15
|
-
- Stage-based quality gates (`draft` -> `final`).
|
|
16
|
-
- Export abstraction (`md` always, `docx`/`pdf` via optional `pandoc`).
|
|
17
|
-
- Example/demo projects (`docs-demo`, `plague-demo`) included in `stego init`.
|
|
18
|
-
|
|
19
|
-
## Getting started (from npm)
|
|
9
|
+
## Quick start (install + init)
|
|
20
10
|
|
|
21
11
|
```bash
|
|
22
12
|
npm install -g stego-cli
|
|
@@ -24,73 +14,69 @@ npm install -g stego-cli
|
|
|
24
14
|
mkdir my-stego-workspace
|
|
25
15
|
cd my-stego-workspace
|
|
26
16
|
stego init
|
|
27
|
-
|
|
28
17
|
npm install
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
npm run export -- --project plague-demo --format md
|
|
18
|
+
|
|
19
|
+
stego list-projects
|
|
20
|
+
stego validate --project fiction-example
|
|
21
|
+
stego build --project fiction-example
|
|
34
22
|
```
|
|
35
23
|
|
|
36
|
-
`stego init` scaffolds
|
|
24
|
+
`stego init` scaffolds two example projects:
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
- `stego-docs` (the full documentation project)
|
|
27
|
+
- `fiction-example` (a fiction-oriented demo with rich Spine usage)
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
For day-to-day editing, open a project folder in VS Code (for example `projects/stego-docs`) and use the Stego VS Code extension, which is the official UI for Stego projects.
|
|
30
|
+
|
|
31
|
+
## Full documentation
|
|
43
32
|
|
|
44
|
-
|
|
33
|
+
The full user documentation lives in the `stego-docs` project.
|
|
45
34
|
|
|
46
|
-
|
|
35
|
+
- In a scaffolded workspace: `projects/stego-docs`
|
|
36
|
+
- In this source repo: `projects/stego-docs`
|
|
47
37
|
|
|
48
|
-
|
|
38
|
+
Start by reading the manuscript files in order, or build the docs project:
|
|
49
39
|
|
|
50
40
|
```bash
|
|
51
|
-
|
|
52
|
-
npm run build -- --project plague-demo
|
|
53
|
-
npm run check-stage -- --project plague-demo --stage proof
|
|
54
|
-
npm run export -- --project plague-demo --format md
|
|
41
|
+
stego build --project stego-docs
|
|
55
42
|
```
|
|
56
43
|
|
|
57
|
-
|
|
44
|
+
## Core commands
|
|
45
|
+
|
|
46
|
+
Run commands from the workspace root and target a project with `--project`.
|
|
58
47
|
|
|
59
48
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
stego list-projects
|
|
50
|
+
stego new-project --project my-book --title "My Book"
|
|
51
|
+
stego validate --project fiction-example
|
|
52
|
+
stego build --project fiction-example
|
|
53
|
+
stego check-stage --project fiction-example --stage revise
|
|
54
|
+
stego export --project fiction-example --format md
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
57
|
+
Projects also include local npm scripts so you can work from inside a project directory.
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
## VS Code workflow
|
|
70
60
|
|
|
71
|
-
|
|
61
|
+
When actively working on one project, open that project directory directly in VS Code (for example `projects/fiction-example`).
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
The Stego VS Code extension is the official UI for Stego projects, and opening a single project keeps its UI context and Spine Browser focused. Project folders also include extension recommendations.
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
## Develop `stego-cli` (this repo)
|
|
76
66
|
|
|
77
67
|
```bash
|
|
78
68
|
npm install
|
|
79
69
|
npm run list-projects
|
|
80
|
-
npm run validate -- --project docs
|
|
81
|
-
npm run build -- --project
|
|
82
|
-
npm run test
|
|
70
|
+
npm run validate -- --project stego-docs
|
|
71
|
+
npm run build -- --project fiction-example
|
|
72
|
+
npm run test
|
|
83
73
|
npm run build:cli
|
|
84
74
|
npm run pack:dry-run
|
|
85
75
|
```
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
## Export requirements (DOCX/PDF)
|
|
77
|
+
## Export requirements (`docx`, `pdf`, `epub`)
|
|
90
78
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Install:
|
|
79
|
+
These formats require `pandoc` on your `PATH`.
|
|
94
80
|
|
|
95
81
|
```bash
|
|
96
82
|
# macOS (Homebrew)
|
|
@@ -106,139 +92,3 @@ sudo apt-get update && sudo apt-get install -y pandoc
|
|
|
106
92
|
# Windows (winget)
|
|
107
93
|
winget install --id JohnMacFarlane.Pandoc -e
|
|
108
94
|
```
|
|
109
|
-
|
|
110
|
-
Verify:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
pandoc --version
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Then run:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
npm run export -- --project plague-demo --format docx
|
|
120
|
-
npm run export -- --project plague-demo --format pdf
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Scaffolded workspace layout
|
|
124
|
-
|
|
125
|
-
- `projects/<project-id>/manuscript/` source manuscript files
|
|
126
|
-
- `projects/<project-id>/spine/` canonical spine category files (`spineCategories[*].notesFile`)
|
|
127
|
-
- `projects/<project-id>/notes/` regular notes and planning docs
|
|
128
|
-
- `projects/<project-id>/dist/` generated outputs only
|
|
129
|
-
- `stego.config.json` workspace configuration
|
|
130
|
-
- `docs/` workflow and conventions
|
|
131
|
-
- `.vscode/tasks.json` root VS Code tasks for common Stego commands
|
|
132
|
-
|
|
133
|
-
## This repo layout (`stego-cli` source)
|
|
134
|
-
|
|
135
|
-
- `tools/` CLI source code and exporters
|
|
136
|
-
- `projects/` template/demo projects bundled by `stego init`
|
|
137
|
-
- `docs/` user-facing docs copied into scaffolded workspaces
|
|
138
|
-
- `.github/workflows/` CI + Changesets release automation
|
|
139
|
-
|
|
140
|
-
## Project spine categories
|
|
141
|
-
|
|
142
|
-
Spine categories are not fixed. Each project can declare them in `stego-project.json` under `spineCategories`.
|
|
143
|
-
|
|
144
|
-
Example:
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"spineCategories": [
|
|
149
|
-
{ "key": "cast", "prefix": "CHAR", "notesFile": "characters.md" },
|
|
150
|
-
{ "key": "places", "prefix": "LOC", "notesFile": "locations.md" },
|
|
151
|
-
{ "key": "incidents", "prefix": "EVENT", "notesFile": "timeline.md" },
|
|
152
|
-
{ "key": "ordinances", "prefix": "STATUTE", "notesFile": "ordinances.md" }
|
|
153
|
-
]
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Use those keys as metadata arrays in manuscript files (for example `cast`, `places`, `incidents`, `ordinances`).
|
|
158
|
-
Each `notesFile` is a filename resolved in `spine/` (for example `spine/characters.md`).
|
|
159
|
-
|
|
160
|
-
If `spineCategories` is omitted or empty, category-based continuity validation is disabled.
|
|
161
|
-
|
|
162
|
-
## Project metadata requirements
|
|
163
|
-
|
|
164
|
-
Base config defaults to `status`.
|
|
165
|
-
|
|
166
|
-
Each project can override required keys in `stego-project.json`:
|
|
167
|
-
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"requiredMetadata": ["status", "chapter", "pov", "timeline"]
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
These keys are advisory and reported as warnings when missing; they do not block validate/build/export.
|
|
175
|
-
Files may omit metadata entirely.
|
|
176
|
-
|
|
177
|
-
## Compile structure (grouped manuscript output)
|
|
178
|
-
|
|
179
|
-
Build grouping is configured per project with `compileStructure.levels`.
|
|
180
|
-
|
|
181
|
-
Example:
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{
|
|
185
|
-
"compileStructure": {
|
|
186
|
-
"levels": [
|
|
187
|
-
{
|
|
188
|
-
"key": "part",
|
|
189
|
-
"label": "Part",
|
|
190
|
-
"titleKey": "part_title",
|
|
191
|
-
"injectHeading": true,
|
|
192
|
-
"headingTemplate": "{label} {value}: {title}",
|
|
193
|
-
"pageBreak": "between-groups"
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
"key": "chapter",
|
|
197
|
-
"label": "Chapter",
|
|
198
|
-
"titleKey": "chapter_title",
|
|
199
|
-
"injectHeading": true,
|
|
200
|
-
"headingTemplate": "{label} {value}: {title}",
|
|
201
|
-
"pageBreak": "between-groups"
|
|
202
|
-
}
|
|
203
|
-
]
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Notes:
|
|
209
|
-
|
|
210
|
-
- `pageBreak` currently supports `none` or `between-groups`.
|
|
211
|
-
- TOC entries are nested by level depth.
|
|
212
|
-
- Missing group key/title values inherit from the previous manuscript file, so you only need to set metadata at structural boundaries.
|
|
213
|
-
- `validate` reports configuration errors for invalid `compileStructure` entries.
|
|
214
|
-
|
|
215
|
-
## Included examples
|
|
216
|
-
|
|
217
|
-
- `plague-demo`: full configuration — rich metadata (`pov`, `timeline`), three spine categories (`characters`, `locations`, `sources`), cross-linked spine with Wikipedia reference links
|
|
218
|
-
- `docs-demo`: nonfiction documentation configuration — no spine categories, freeform notes only, primarily `status` metadata
|
|
219
|
-
|
|
220
|
-
## Placeholder edit workflow (`{{...}}` + Cmd+I)
|
|
221
|
-
|
|
222
|
-
This repo includes Copilot instruction files to keep placeholder edits scoped:
|
|
223
|
-
|
|
224
|
-
- `.github/copilot-instructions.md`
|
|
225
|
-
- `.github/instructions/placeholder-fill.instructions.md`
|
|
226
|
-
|
|
227
|
-
Placeholder convention:
|
|
228
|
-
|
|
229
|
-
- Write draft placeholders as `{{...}}` in manuscript prose.
|
|
230
|
-
- Select the placeholder text and run Cmd+I.
|
|
231
|
-
- Use short prompts like `fill placeholder` or `replace only inside {{}}`.
|
|
232
|
-
|
|
233
|
-
Expected behavior:
|
|
234
|
-
|
|
235
|
-
- Replace only the content inside braces.
|
|
236
|
-
- Preserve surrounding sentence/paragraph text.
|
|
237
|
-
|
|
238
|
-
## Next Steps
|
|
239
|
-
|
|
240
|
-
- Add Mermaid graphs of metadata (entity relationships, co-occurrence, chapter sequence).
|
|
241
|
-
|
|
242
|
-
## VS Code tasks
|
|
243
|
-
|
|
244
|
-
Tasks are defined in `.vscode/tasks.json` for validate/build/stage-check/export.
|
package/dist/stego-cli.js
CHANGED
|
@@ -34,25 +34,26 @@ This directory is a Stego writing workspace (a monorepo for one or more writing
|
|
|
34
34
|
## What was scaffolded
|
|
35
35
|
|
|
36
36
|
- \`stego.config.json\` workspace configuration
|
|
37
|
-
- \`projects/\` demo projects (\`docs
|
|
38
|
-
- \`docs/\` workflow and conventions docs
|
|
37
|
+
- \`projects/\` demo projects (\`stego-docs\` and \`fiction-example\`)
|
|
39
38
|
- root \`package.json\` scripts for Stego commands
|
|
40
39
|
- root \`.vscode/tasks.json\` tasks for common workflows
|
|
41
40
|
|
|
41
|
+
Full documentation lives in \`projects/stego-docs\`.
|
|
42
|
+
|
|
42
43
|
## First run
|
|
43
44
|
|
|
44
45
|
\`\`\`bash
|
|
45
46
|
npm install
|
|
46
|
-
|
|
47
|
+
stego list-projects
|
|
47
48
|
\`\`\`
|
|
48
49
|
|
|
49
50
|
## Run commands for a specific project (from workspace root)
|
|
50
51
|
|
|
51
52
|
\`\`\`bash
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
stego validate --project fiction-example
|
|
54
|
+
stego build --project fiction-example
|
|
55
|
+
stego check-stage --project fiction-example --stage revise
|
|
56
|
+
stego export --project fiction-example --format md
|
|
56
57
|
\`\`\`
|
|
57
58
|
|
|
58
59
|
## Work inside one project
|
|
@@ -60,14 +61,14 @@ npm run export -- --project plague-demo --format md
|
|
|
60
61
|
Each project also has local scripts, so you can run commands from inside a project directory:
|
|
61
62
|
|
|
62
63
|
\`\`\`bash
|
|
63
|
-
cd projects/
|
|
64
|
+
cd projects/fiction-example
|
|
64
65
|
npm run validate
|
|
65
66
|
npm run build
|
|
66
67
|
\`\`\`
|
|
67
68
|
|
|
68
69
|
## VS Code recommendation
|
|
69
70
|
|
|
70
|
-
When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/
|
|
71
|
+
When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/fiction-example\`).
|
|
71
72
|
|
|
72
73
|
This keeps your editor context focused and applies the project's recommended extensions (including Stego + Saurus) for that project.
|
|
73
74
|
|
|
@@ -156,6 +157,18 @@ async function main() {
|
|
|
156
157
|
}
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
160
|
+
case "lint": {
|
|
161
|
+
activateWorkspace(options);
|
|
162
|
+
const project = resolveProject(readStringOption(options, "project"));
|
|
163
|
+
const selection = resolveLintSelection(options);
|
|
164
|
+
const result = runProjectLint(project, selection);
|
|
165
|
+
printReport(result.issues);
|
|
166
|
+
exitIfErrors(result.issues);
|
|
167
|
+
const scopeLabel = formatLintSelection(selection);
|
|
168
|
+
const fileLabel = result.fileCount === 1 ? "file" : "files";
|
|
169
|
+
logLine(`Lint passed for '${project.id}' (${scopeLabel}, ${result.fileCount} ${fileLabel}).`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
159
172
|
case "export": {
|
|
160
173
|
activateWorkspace(options);
|
|
161
174
|
const project = resolveProject(readStringOption(options, "project"));
|
|
@@ -463,9 +476,9 @@ async function initWorkspace(options) {
|
|
|
463
476
|
writeScaffoldGitignore(targetRoot, copiedPaths);
|
|
464
477
|
writeScaffoldReadme(targetRoot, copiedPaths);
|
|
465
478
|
copyTemplateAsset(".markdownlint.json", targetRoot, copiedPaths);
|
|
479
|
+
copyTemplateAsset(".markdownlint.manuscript.json", targetRoot, copiedPaths);
|
|
466
480
|
copyTemplateAsset(".cspell.json", targetRoot, copiedPaths);
|
|
467
481
|
copyTemplateAsset(ROOT_CONFIG_FILENAME, targetRoot, copiedPaths);
|
|
468
|
-
copyTemplateAsset("docs", targetRoot, copiedPaths);
|
|
469
482
|
copyTemplateAsset("projects", targetRoot, copiedPaths);
|
|
470
483
|
copyTemplateAsset(path.join(".vscode", "tasks.json"), targetRoot, copiedPaths);
|
|
471
484
|
copyTemplateAsset(path.join(".vscode", "extensions.json"), targetRoot, copiedPaths, { optional: true });
|
|
@@ -483,8 +496,9 @@ async function initWorkspace(options) {
|
|
|
483
496
|
logLine("");
|
|
484
497
|
logLine("Next steps:");
|
|
485
498
|
logLine(" npm install");
|
|
486
|
-
logLine("
|
|
487
|
-
logLine("
|
|
499
|
+
logLine(" stego list-projects");
|
|
500
|
+
logLine(" stego validate --project fiction-example");
|
|
501
|
+
logLine(" stego build --project fiction-example");
|
|
488
502
|
}
|
|
489
503
|
async function promptYesNo(question, defaultYes) {
|
|
490
504
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -589,9 +603,6 @@ function rewriteTemplateProjectPackageScripts(targetRoot) {
|
|
|
589
603
|
const scripts = isPlainObject(projectPackage.scripts)
|
|
590
604
|
? { ...projectPackage.scripts }
|
|
591
605
|
: {};
|
|
592
|
-
if (typeof projectPackage.name === "string" && projectPackage.name.startsWith("writing-project-")) {
|
|
593
|
-
projectPackage.name = projectPackage.name.replace(/^writing-project-/, "stego-project-");
|
|
594
|
-
}
|
|
595
606
|
scripts.validate = "npx --no-install stego validate";
|
|
596
607
|
scripts.build = "npx --no-install stego build";
|
|
597
608
|
scripts["check-stage"] = "npx --no-install stego check-stage";
|
|
@@ -686,6 +697,7 @@ function writeInitRootPackageJson(targetRoot) {
|
|
|
686
697
|
scripts: {
|
|
687
698
|
"list-projects": "stego list-projects",
|
|
688
699
|
"new-project": "stego new-project",
|
|
700
|
+
lint: "stego lint",
|
|
689
701
|
validate: "stego validate",
|
|
690
702
|
build: "stego build",
|
|
691
703
|
"check-stage": "stego check-stage",
|
|
@@ -700,7 +712,7 @@ function writeInitRootPackageJson(targetRoot) {
|
|
|
700
712
|
fs.writeFileSync(path.join(targetRoot, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
701
713
|
}
|
|
702
714
|
function printUsage() {
|
|
703
|
-
console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
|
|
715
|
+
console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n lint --project <project-id> [--manuscript|--spine] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
|
|
704
716
|
}
|
|
705
717
|
function listProjects() {
|
|
706
718
|
const ids = getProjectIds();
|
|
@@ -731,6 +743,7 @@ async function createProject(projectIdOption, titleOption) {
|
|
|
731
743
|
const notesDir = path.join(projectRoot, config.notesDir);
|
|
732
744
|
fs.mkdirSync(notesDir, { recursive: true });
|
|
733
745
|
fs.mkdirSync(path.join(projectRoot, config.distDir), { recursive: true });
|
|
746
|
+
const manuscriptDir = path.join(projectRoot, config.chapterDir);
|
|
734
747
|
const projectJson = {
|
|
735
748
|
id: projectId,
|
|
736
749
|
title: titleOption?.trim() || toDisplayTitle(projectId),
|
|
@@ -761,6 +774,7 @@ async function createProject(projectIdOption, titleOption) {
|
|
|
761
774
|
name: `stego-project-${projectId}`,
|
|
762
775
|
private: true,
|
|
763
776
|
scripts: {
|
|
777
|
+
lint: "npx --no-install stego lint",
|
|
764
778
|
validate: "npx --no-install stego validate",
|
|
765
779
|
build: "npx --no-install stego build",
|
|
766
780
|
"check-stage": "npx --no-install stego check-stage",
|
|
@@ -769,6 +783,17 @@ async function createProject(projectIdOption, titleOption) {
|
|
|
769
783
|
};
|
|
770
784
|
const projectPackagePath = path.join(projectRoot, "package.json");
|
|
771
785
|
fs.writeFileSync(projectPackagePath, `${JSON.stringify(projectPackage, null, 2)}\n`, "utf8");
|
|
786
|
+
const starterManuscriptPath = path.join(manuscriptDir, "100-hello-world.md");
|
|
787
|
+
fs.writeFileSync(starterManuscriptPath, `---
|
|
788
|
+
status: draft
|
|
789
|
+
chapter: 1
|
|
790
|
+
chapter_title: Hello World
|
|
791
|
+
---
|
|
792
|
+
|
|
793
|
+
# Hello World
|
|
794
|
+
|
|
795
|
+
Start writing here.
|
|
796
|
+
`, "utf8");
|
|
772
797
|
const charactersNotesPath = path.join(spineDir, "characters.md");
|
|
773
798
|
fs.writeFileSync(charactersNotesPath, "# Characters\n\n", "utf8");
|
|
774
799
|
const projectExtensionsPath = path.join(projectRoot, ".vscode", "extensions.json");
|
|
@@ -781,6 +806,7 @@ async function createProject(projectIdOption, titleOption) {
|
|
|
781
806
|
logLine(`Created project: ${path.relative(repoRoot, projectRoot)}`);
|
|
782
807
|
logLine(`- ${path.relative(repoRoot, projectJsonPath)}`);
|
|
783
808
|
logLine(`- ${path.relative(repoRoot, projectPackagePath)}`);
|
|
809
|
+
logLine(`- ${path.relative(repoRoot, starterManuscriptPath)}`);
|
|
784
810
|
logLine(`- ${path.relative(repoRoot, charactersNotesPath)}`);
|
|
785
811
|
logLine(`- ${path.relative(repoRoot, projectExtensionsPath)}`);
|
|
786
812
|
if (projectSettingsPath) {
|
|
@@ -1604,10 +1630,10 @@ function runStageCheck(project, runtimeConfig, stage, onlyFile) {
|
|
|
1604
1630
|
const chapterPaths = report.chapters.map((chapter) => chapter.path);
|
|
1605
1631
|
const spineWords = collectSpineWordsForSpellcheck(report.spineState.ids);
|
|
1606
1632
|
if (policy.enforceMarkdownlint) {
|
|
1607
|
-
issues.push(...runMarkdownlint(project, chapterPaths, true));
|
|
1633
|
+
issues.push(...runMarkdownlint(project, chapterPaths, true, "manuscript"));
|
|
1608
1634
|
}
|
|
1609
1635
|
else {
|
|
1610
|
-
issues.push(...runMarkdownlint(project, chapterPaths, false));
|
|
1636
|
+
issues.push(...runMarkdownlint(project, chapterPaths, false, "manuscript"));
|
|
1611
1637
|
}
|
|
1612
1638
|
if (policy.enforceCSpell) {
|
|
1613
1639
|
issues.push(...runCSpell(chapterPaths, true, spineWords));
|
|
@@ -1617,20 +1643,133 @@ function runStageCheck(project, runtimeConfig, stage, onlyFile) {
|
|
|
1617
1643
|
}
|
|
1618
1644
|
return { chapters: report.chapters, issues };
|
|
1619
1645
|
}
|
|
1620
|
-
function
|
|
1646
|
+
function resolveLintSelection(options) {
|
|
1647
|
+
const manuscript = readBooleanOption(options, "manuscript");
|
|
1648
|
+
const spine = readBooleanOption(options, "spine");
|
|
1649
|
+
if (!manuscript && !spine) {
|
|
1650
|
+
return { manuscript: true, spine: true };
|
|
1651
|
+
}
|
|
1652
|
+
return { manuscript, spine };
|
|
1653
|
+
}
|
|
1654
|
+
function formatLintSelection(selection) {
|
|
1655
|
+
if (selection.manuscript && selection.spine) {
|
|
1656
|
+
return "manuscript + spine";
|
|
1657
|
+
}
|
|
1658
|
+
if (selection.manuscript) {
|
|
1659
|
+
return "manuscript";
|
|
1660
|
+
}
|
|
1661
|
+
if (selection.spine) {
|
|
1662
|
+
return "spine";
|
|
1663
|
+
}
|
|
1664
|
+
return "none";
|
|
1665
|
+
}
|
|
1666
|
+
function runProjectLint(project, selection) {
|
|
1667
|
+
const issues = [];
|
|
1668
|
+
let fileCount = 0;
|
|
1669
|
+
if (selection.manuscript) {
|
|
1670
|
+
const manuscriptFiles = collectTopLevelMarkdownFiles(project.manuscriptDir);
|
|
1671
|
+
if (manuscriptFiles.length === 0) {
|
|
1672
|
+
issues.push(makeIssue("error", "lint", `No manuscript markdown files found in ${project.manuscriptDir}`));
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
fileCount += manuscriptFiles.length;
|
|
1676
|
+
issues.push(...runMarkdownlint(project, manuscriptFiles, true, "manuscript"));
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
if (selection.spine) {
|
|
1680
|
+
const spineLintState = collectSpineLintMarkdownFiles(project);
|
|
1681
|
+
issues.push(...spineLintState.issues);
|
|
1682
|
+
if (spineLintState.files.length > 0) {
|
|
1683
|
+
fileCount += spineLintState.files.length;
|
|
1684
|
+
issues.push(...runMarkdownlint(project, spineLintState.files, true, "default"));
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
if (fileCount === 0 && issues.length === 0) {
|
|
1688
|
+
issues.push(makeIssue("error", "lint", `No markdown files found for lint scope '${formatLintSelection(selection)}' in project '${project.id}'.`));
|
|
1689
|
+
}
|
|
1690
|
+
return { issues, fileCount };
|
|
1691
|
+
}
|
|
1692
|
+
function collectSpineLintMarkdownFiles(project) {
|
|
1693
|
+
const issues = [];
|
|
1694
|
+
const files = new Set();
|
|
1695
|
+
addMarkdownFilesFromDirectory(files, project.spineDir, true);
|
|
1696
|
+
if (!fs.existsSync(project.spineDir)) {
|
|
1697
|
+
issues.push(makeIssue("warning", "lint", `Missing spine directory: ${project.spineDir}`));
|
|
1698
|
+
}
|
|
1699
|
+
addMarkdownFilesFromDirectory(files, project.notesDir, true);
|
|
1700
|
+
if (!fs.existsSync(project.notesDir)) {
|
|
1701
|
+
issues.push(makeIssue("warning", "lint", `Missing notes directory: ${project.notesDir}`));
|
|
1702
|
+
}
|
|
1703
|
+
for (const file of collectTopLevelMarkdownFiles(project.root)) {
|
|
1704
|
+
files.add(file);
|
|
1705
|
+
}
|
|
1706
|
+
const sortedFiles = Array.from(files).sort();
|
|
1707
|
+
if (sortedFiles.length === 0) {
|
|
1708
|
+
issues.push(makeIssue("error", "lint", `No spine/notes markdown files found in ${project.spineDir}, ${project.notesDir}, or project root.`));
|
|
1709
|
+
}
|
|
1710
|
+
return { files: sortedFiles, issues };
|
|
1711
|
+
}
|
|
1712
|
+
function collectTopLevelMarkdownFiles(directory) {
|
|
1713
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
1714
|
+
return [];
|
|
1715
|
+
}
|
|
1716
|
+
return fs
|
|
1717
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
1718
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
1719
|
+
.map((entry) => path.join(directory, entry.name))
|
|
1720
|
+
.sort();
|
|
1721
|
+
}
|
|
1722
|
+
function addMarkdownFilesFromDirectory(target, directory, recursive) {
|
|
1723
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const stack = [directory];
|
|
1727
|
+
while (stack.length > 0) {
|
|
1728
|
+
const current = stack.pop();
|
|
1729
|
+
if (!current) {
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
const entries = fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1733
|
+
for (const entry of entries) {
|
|
1734
|
+
const fullPath = path.join(current, entry.name);
|
|
1735
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1736
|
+
target.add(fullPath);
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (recursive && entry.isDirectory()) {
|
|
1740
|
+
stack.push(fullPath);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function runMarkdownlint(project, files, required, profile = "default") {
|
|
1746
|
+
if (files.length === 0) {
|
|
1747
|
+
return [];
|
|
1748
|
+
}
|
|
1621
1749
|
const markdownlintCommand = resolveCommand("markdownlint");
|
|
1622
1750
|
if (!markdownlintCommand) {
|
|
1623
1751
|
if (required) {
|
|
1624
1752
|
return [
|
|
1625
|
-
makeIssue("error", "tooling", "markdownlint is required for this
|
|
1753
|
+
makeIssue("error", "tooling", "markdownlint is required for this command but not installed. Run 'npm i' in the repo root.")
|
|
1626
1754
|
];
|
|
1627
1755
|
}
|
|
1628
1756
|
return [];
|
|
1629
1757
|
}
|
|
1630
|
-
const
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1758
|
+
const manuscriptProjectConfigPath = path.join(project.root, ".markdownlint.manuscript.json");
|
|
1759
|
+
const manuscriptRepoConfigPath = path.join(repoRoot, ".markdownlint.manuscript.json");
|
|
1760
|
+
const defaultProjectConfigPath = path.join(project.root, ".markdownlint.json");
|
|
1761
|
+
const defaultRepoConfigPath = path.join(repoRoot, ".markdownlint.json");
|
|
1762
|
+
const markdownlintConfigPath = profile === "manuscript"
|
|
1763
|
+
? (fs.existsSync(manuscriptProjectConfigPath)
|
|
1764
|
+
? manuscriptProjectConfigPath
|
|
1765
|
+
: fs.existsSync(manuscriptRepoConfigPath)
|
|
1766
|
+
? manuscriptRepoConfigPath
|
|
1767
|
+
: fs.existsSync(defaultProjectConfigPath)
|
|
1768
|
+
? defaultProjectConfigPath
|
|
1769
|
+
: defaultRepoConfigPath)
|
|
1770
|
+
: (fs.existsSync(defaultProjectConfigPath)
|
|
1771
|
+
? defaultProjectConfigPath
|
|
1772
|
+
: defaultRepoConfigPath);
|
|
1634
1773
|
const prepared = prepareFilesWithoutComments(files);
|
|
1635
1774
|
try {
|
|
1636
1775
|
const result = spawnSync(markdownlintCommand, ["--config", markdownlintConfigPath, ...prepared.files], {
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stego-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Installable CLI for the Stego writing monorepo workflow.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/matt-gold/stego.git"
|
|
9
|
+
"url": "https://github.com/matt-gold/stego-cli.git"
|
|
10
10
|
},
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/matt-gold/stego/issues"
|
|
12
|
+
"url": "https://github.com/matt-gold/stego-cli/issues"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.com/matt-gold/stego#readme",
|
|
14
|
+
"homepage": "https://github.com/matt-gold/stego-cli#readme",
|
|
15
15
|
"bin": {
|
|
16
16
|
"stego": "dist/stego-cli.js"
|
|
17
17
|
},
|
|
@@ -20,14 +20,15 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist/**",
|
|
23
|
-
"docs/**",
|
|
24
23
|
"projects/**",
|
|
24
|
+
"!projects/local-only/**",
|
|
25
25
|
"!projects/*/dist/*.md",
|
|
26
26
|
"!projects/*/dist/exports/**",
|
|
27
27
|
".vscode/tasks.json",
|
|
28
28
|
".vscode/extensions.json",
|
|
29
29
|
".gitignore",
|
|
30
30
|
".markdownlint.json",
|
|
31
|
+
".markdownlint.manuscript.json",
|
|
31
32
|
".cspell.json",
|
|
32
33
|
"stego.config.json",
|
|
33
34
|
"README.md"
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"release:version": "changeset version",
|
|
42
43
|
"list-projects": "node --experimental-strip-types tools/stego-cli.ts list-projects",
|
|
43
44
|
"new-project": "node --experimental-strip-types tools/stego-cli.ts new-project",
|
|
45
|
+
"lint": "node --experimental-strip-types tools/stego-cli.ts lint",
|
|
44
46
|
"validate": "node --experimental-strip-types tools/stego-cli.ts validate",
|
|
45
47
|
"build": "node --experimental-strip-types tools/stego-cli.ts build",
|
|
46
48
|
"check-stage": "node --experimental-strip-types tools/stego-cli.ts check-stage",
|