stego-cli 0.1.2 → 0.1.4
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 +75 -10
- package/dist/stego-cli.js +162 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# Stego
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Stego is a Markdown-first creative writing workflow packaged as an installable CLI: `stego-cli`.
|
|
4
|
+
|
|
5
|
+
This repository is the source for the CLI and the example/template content that `stego init` scaffolds into a new workspace.
|
|
4
6
|
|
|
5
7
|
## What this includes
|
|
6
8
|
|
|
9
|
+
- Installable CLI (`stego`) for creating and operating Stego workspaces
|
|
7
10
|
- Project-per-folder structure inside one monorepo.
|
|
8
11
|
- Flexible manuscript files with per-project metadata requirements.
|
|
9
12
|
- Configurable manuscript grouping via `compileStructure.levels` (for example `part` + `chapter`).
|
|
@@ -11,23 +14,77 @@ This workspace is a Markdown-first creative writing pipeline for short stories t
|
|
|
11
14
|
- Deterministic build into one manuscript Markdown file.
|
|
12
15
|
- Stage-based quality gates (`draft` -> `final`).
|
|
13
16
|
- Export abstraction (`md` always, `docx`/`pdf` via optional `pandoc`).
|
|
14
|
-
-
|
|
17
|
+
- Example/demo projects (`docs-demo`, `plague-demo`) included in `stego init`.
|
|
15
18
|
|
|
16
|
-
##
|
|
19
|
+
## Getting started (from npm)
|
|
17
20
|
|
|
18
21
|
```bash
|
|
19
|
-
|
|
22
|
+
npm install -g stego-cli
|
|
23
|
+
|
|
24
|
+
mkdir my-stego-workspace
|
|
25
|
+
cd my-stego-workspace
|
|
26
|
+
stego init
|
|
27
|
+
|
|
28
|
+
npm install
|
|
20
29
|
npm run list-projects
|
|
21
|
-
npm run new-project -- --project my-new-project --title "My New Project"
|
|
22
30
|
npm run validate -- --project plague-demo
|
|
23
31
|
npm run build -- --project plague-demo
|
|
24
32
|
npm run check-stage -- --project plague-demo --stage revise
|
|
25
33
|
npm run export -- --project plague-demo --format md
|
|
26
34
|
```
|
|
27
35
|
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
`stego init` scaffolds a new workspace in the current directory (it must be empty unless you pass `--force`).
|
|
37
|
+
|
|
38
|
+
Create another project in the workspace:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
stego new-project --project my-new-project --title "My New Project"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`stego new-project` scaffolds `manuscript/`, `spine/`, `notes/`, and `dist/`, seeds `stego-project.json`, creates a project-local `package.json`, and writes `.vscode/extensions.json` recommendations (Stego + Saurus) for that project.
|
|
45
|
+
|
|
46
|
+
## Running commands for a specific project
|
|
47
|
+
|
|
48
|
+
From the workspace root, target a project with `--project`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run validate -- --project plague-demo
|
|
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
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Each project also has local scripts, so you can work from inside the project directory:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd projects/plague-demo
|
|
61
|
+
npm run validate
|
|
62
|
+
npm run build
|
|
63
|
+
npm run check-stage -- --stage proof
|
|
64
|
+
npm run export -- --format md
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## VS Code workflow
|
|
68
|
+
|
|
69
|
+
When you are actively working on one project, open that project directory directly in VS Code (for example `projects/plague-demo`) rather than the whole workspace.
|
|
70
|
+
|
|
71
|
+
This keeps editor context focused and applies the project's recommended extensions via `projects/<project-id>/.vscode/extensions.json`.
|
|
72
|
+
|
|
73
|
+
## Developing `stego-cli` (this repo)
|
|
74
|
+
|
|
75
|
+
If you are working on the CLI itself (this repository), use the local development scripts:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install
|
|
79
|
+
npm run list-projects
|
|
80
|
+
npm run validate -- --project docs-demo
|
|
81
|
+
npm run build -- --project plague-demo
|
|
82
|
+
npm run test:compile-structure
|
|
83
|
+
npm run build:cli
|
|
84
|
+
npm run pack:dry-run
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
These source-repo scripts run the TypeScript CLI directly with Node (`--experimental-strip-types`) for local development.
|
|
31
88
|
|
|
32
89
|
## Export requirements (DOCX/PDF)
|
|
33
90
|
|
|
@@ -63,14 +120,22 @@ npm run export -- --project plague-demo --format docx
|
|
|
63
120
|
npm run export -- --project plague-demo --format pdf
|
|
64
121
|
```
|
|
65
122
|
|
|
66
|
-
##
|
|
123
|
+
## Scaffolded workspace layout
|
|
67
124
|
|
|
68
125
|
- `projects/<project-id>/manuscript/` source manuscript files
|
|
69
126
|
- `projects/<project-id>/spine/` canonical spine category files (`spineCategories[*].notesFile`)
|
|
70
127
|
- `projects/<project-id>/notes/` regular notes and planning docs
|
|
71
128
|
- `projects/<project-id>/dist/` generated outputs only
|
|
129
|
+
- `stego.config.json` workspace configuration
|
|
72
130
|
- `docs/` workflow and conventions
|
|
73
|
-
- `
|
|
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
|
|
74
139
|
|
|
75
140
|
## Project spine categories
|
|
76
141
|
|
package/dist/stego-cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { markdownExporter } from "./exporters/markdown-exporter.js";
|
|
9
10
|
import { createPandocExporter } from "./exporters/pandoc-exporter.js";
|
|
@@ -16,6 +17,7 @@ const STATUS_RANK = {
|
|
|
16
17
|
};
|
|
17
18
|
const RESERVED_COMMENT_PREFIX = "CMT";
|
|
18
19
|
const ROOT_CONFIG_FILENAME = "stego.config.json";
|
|
20
|
+
const PROSE_FONT_PROMPT = "Switch workspace to proportional (prose-style) font? (recommended)";
|
|
19
21
|
const SCAFFOLD_GITIGNORE_CONTENT = `node_modules/
|
|
20
22
|
/dist/
|
|
21
23
|
.DS_Store
|
|
@@ -25,6 +27,67 @@ projects/*/dist/*
|
|
|
25
27
|
projects/*/.vscode/settings.json
|
|
26
28
|
.vscode/settings.json
|
|
27
29
|
`;
|
|
30
|
+
const SCAFFOLD_README_CONTENT = `# Stego Workspace
|
|
31
|
+
|
|
32
|
+
This directory is a Stego writing workspace (a monorepo for one or more writing projects).
|
|
33
|
+
|
|
34
|
+
## What was scaffolded
|
|
35
|
+
|
|
36
|
+
- \`stego.config.json\` workspace configuration
|
|
37
|
+
- \`projects/\` demo projects (\`docs-demo\` and \`plague-demo\`)
|
|
38
|
+
- \`docs/\` workflow and conventions docs
|
|
39
|
+
- root \`package.json\` scripts for Stego commands
|
|
40
|
+
- root \`.vscode/tasks.json\` tasks for common workflows
|
|
41
|
+
|
|
42
|
+
## First run
|
|
43
|
+
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
npm install
|
|
46
|
+
npm run list-projects
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
## Run commands for a specific project (from workspace root)
|
|
50
|
+
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
npm run validate -- --project plague-demo
|
|
53
|
+
npm run build -- --project plague-demo
|
|
54
|
+
npm run check-stage -- --project plague-demo --stage revise
|
|
55
|
+
npm run export -- --project plague-demo --format md
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## Work inside one project
|
|
59
|
+
|
|
60
|
+
Each project also has local scripts, so you can run commands from inside a project directory:
|
|
61
|
+
|
|
62
|
+
\`\`\`bash
|
|
63
|
+
cd projects/plague-demo
|
|
64
|
+
npm run validate
|
|
65
|
+
npm run build
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
## VS Code recommendation
|
|
69
|
+
|
|
70
|
+
When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/plague-demo\`).
|
|
71
|
+
|
|
72
|
+
This keeps your editor context focused and applies the project's recommended extensions (including Stego + Saurus) for that project.
|
|
73
|
+
|
|
74
|
+
## Create a new project
|
|
75
|
+
|
|
76
|
+
\`\`\`bash
|
|
77
|
+
stego new-project --project my-book --title "My Book"
|
|
78
|
+
\`\`\`
|
|
79
|
+
`;
|
|
80
|
+
const PROSE_MARKDOWN_EDITOR_SETTINGS = {
|
|
81
|
+
"[markdown]": {
|
|
82
|
+
"editor.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif",
|
|
83
|
+
"editor.fontSize": 17,
|
|
84
|
+
"editor.lineHeight": 28,
|
|
85
|
+
"editor.wordWrap": "wordWrapColumn",
|
|
86
|
+
"editor.wordWrapColumn": 72,
|
|
87
|
+
"editor.lineNumbers": "off"
|
|
88
|
+
},
|
|
89
|
+
"markdown.preview.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
90
|
+
};
|
|
28
91
|
const PROJECT_EXTENSION_RECOMMENDATIONS = [
|
|
29
92
|
"matt-gold.stego-extension",
|
|
30
93
|
"matt-gold.saurus-extension"
|
|
@@ -33,8 +96,8 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
33
96
|
const packageRoot = path.resolve(scriptDir, "..");
|
|
34
97
|
let repoRoot = "";
|
|
35
98
|
let config;
|
|
36
|
-
main();
|
|
37
|
-
function main() {
|
|
99
|
+
void main();
|
|
100
|
+
async function main() {
|
|
38
101
|
const { command, options } = parseArgs(process.argv.slice(2));
|
|
39
102
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
40
103
|
printUsage();
|
|
@@ -43,7 +106,7 @@ function main() {
|
|
|
43
106
|
try {
|
|
44
107
|
switch (command) {
|
|
45
108
|
case "init":
|
|
46
|
-
initWorkspace({ force: readBooleanOption(options, "force") });
|
|
109
|
+
await initWorkspace({ force: readBooleanOption(options, "force") });
|
|
47
110
|
return;
|
|
48
111
|
case "list-projects":
|
|
49
112
|
activateWorkspace(options);
|
|
@@ -51,7 +114,7 @@ function main() {
|
|
|
51
114
|
return;
|
|
52
115
|
case "new-project":
|
|
53
116
|
activateWorkspace(options);
|
|
54
|
-
createProject(readStringOption(options, "project"), readStringOption(options, "title"));
|
|
117
|
+
await createProject(readStringOption(options, "project"), readStringOption(options, "title"));
|
|
55
118
|
return;
|
|
56
119
|
case "validate": {
|
|
57
120
|
activateWorkspace(options);
|
|
@@ -388,7 +451,7 @@ function findNearestFileUpward(startPath, filename) {
|
|
|
388
451
|
current = parent;
|
|
389
452
|
}
|
|
390
453
|
}
|
|
391
|
-
function initWorkspace(options) {
|
|
454
|
+
async function initWorkspace(options) {
|
|
392
455
|
const targetRoot = process.cwd();
|
|
393
456
|
const entries = fs
|
|
394
457
|
.readdirSync(targetRoot, { withFileTypes: true })
|
|
@@ -398,6 +461,7 @@ function initWorkspace(options) {
|
|
|
398
461
|
}
|
|
399
462
|
const copiedPaths = [];
|
|
400
463
|
writeScaffoldGitignore(targetRoot, copiedPaths);
|
|
464
|
+
writeScaffoldReadme(targetRoot, copiedPaths);
|
|
401
465
|
copyTemplateAsset(".markdownlint.json", targetRoot, copiedPaths);
|
|
402
466
|
copyTemplateAsset(".cspell.json", targetRoot, copiedPaths);
|
|
403
467
|
copyTemplateAsset(ROOT_CONFIG_FILENAME, targetRoot, copiedPaths);
|
|
@@ -406,6 +470,10 @@ function initWorkspace(options) {
|
|
|
406
470
|
copyTemplateAsset(path.join(".vscode", "tasks.json"), targetRoot, copiedPaths);
|
|
407
471
|
copyTemplateAsset(path.join(".vscode", "extensions.json"), targetRoot, copiedPaths, { optional: true });
|
|
408
472
|
rewriteTemplateProjectPackageScripts(targetRoot);
|
|
473
|
+
const enableProseFont = await promptYesNo(PROSE_FONT_PROMPT, true);
|
|
474
|
+
if (enableProseFont) {
|
|
475
|
+
writeProjectProseEditorSettings(targetRoot, copiedPaths);
|
|
476
|
+
}
|
|
409
477
|
writeInitRootPackageJson(targetRoot);
|
|
410
478
|
logLine(`Initialized Stego workspace in ${targetRoot}`);
|
|
411
479
|
for (const relativePath of copiedPaths) {
|
|
@@ -418,6 +486,34 @@ function initWorkspace(options) {
|
|
|
418
486
|
logLine(" npm run list-projects");
|
|
419
487
|
logLine(" npm run validate -- --project plague-demo");
|
|
420
488
|
}
|
|
489
|
+
async function promptYesNo(question, defaultYes) {
|
|
490
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
491
|
+
return defaultYes;
|
|
492
|
+
}
|
|
493
|
+
const rl = createInterface({
|
|
494
|
+
input: process.stdin,
|
|
495
|
+
output: process.stdout
|
|
496
|
+
});
|
|
497
|
+
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
|
|
498
|
+
try {
|
|
499
|
+
while (true) {
|
|
500
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
501
|
+
if (!answer) {
|
|
502
|
+
return defaultYes;
|
|
503
|
+
}
|
|
504
|
+
if (answer === "y" || answer === "yes") {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
if (answer === "n" || answer === "no") {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
console.log("Please answer y or n.");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
finally {
|
|
514
|
+
rl.close();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
421
517
|
function copyTemplateAsset(sourceRelativePath, targetRoot, copiedPaths, options) {
|
|
422
518
|
const sourcePath = path.join(packageRoot, sourceRelativePath);
|
|
423
519
|
if (!fs.existsSync(sourcePath)) {
|
|
@@ -447,6 +543,11 @@ function writeScaffoldGitignore(targetRoot, copiedPaths) {
|
|
|
447
543
|
fs.writeFileSync(destinationPath, SCAFFOLD_GITIGNORE_CONTENT, "utf8");
|
|
448
544
|
copiedPaths.push(".gitignore");
|
|
449
545
|
}
|
|
546
|
+
function writeScaffoldReadme(targetRoot, copiedPaths) {
|
|
547
|
+
const destinationPath = path.join(targetRoot, "README.md");
|
|
548
|
+
fs.writeFileSync(destinationPath, SCAFFOLD_README_CONTENT, "utf8");
|
|
549
|
+
copiedPaths.push("README.md");
|
|
550
|
+
}
|
|
450
551
|
function shouldCopyTemplatePath(currentSourcePath) {
|
|
451
552
|
const relativePath = path.relative(packageRoot, currentSourcePath);
|
|
452
553
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
@@ -524,6 +625,53 @@ function ensureProjectExtensionsRecommendations(projectRoot) {
|
|
|
524
625
|
};
|
|
525
626
|
fs.writeFileSync(extensionsPath, `${JSON.stringify(extensionsConfig, null, 2)}\n`, "utf8");
|
|
526
627
|
}
|
|
628
|
+
function writeProjectProseEditorSettings(targetRoot, copiedPaths) {
|
|
629
|
+
const projectsRoot = path.join(targetRoot, "projects");
|
|
630
|
+
if (!fs.existsSync(projectsRoot)) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
for (const entry of fs.readdirSync(projectsRoot, { withFileTypes: true })) {
|
|
634
|
+
if (!entry.isDirectory()) {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const projectRoot = path.join(projectsRoot, entry.name);
|
|
638
|
+
const settingsPath = writeProseEditorSettingsForProject(projectRoot);
|
|
639
|
+
copiedPaths.push(path.relative(targetRoot, settingsPath));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function writeProseEditorSettingsForProject(projectRoot) {
|
|
643
|
+
const vscodeDir = path.join(projectRoot, ".vscode");
|
|
644
|
+
const settingsPath = path.join(vscodeDir, "settings.json");
|
|
645
|
+
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
646
|
+
let existingSettings = {};
|
|
647
|
+
if (fs.existsSync(settingsPath)) {
|
|
648
|
+
try {
|
|
649
|
+
const parsed = readJson(settingsPath);
|
|
650
|
+
if (isPlainObject(parsed)) {
|
|
651
|
+
existingSettings = parsed;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
existingSettings = {};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const proseMarkdownSettings = isPlainObject(PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"])
|
|
659
|
+
? PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"]
|
|
660
|
+
: {};
|
|
661
|
+
const existingMarkdownSettings = isPlainObject(existingSettings["[markdown]"])
|
|
662
|
+
? existingSettings["[markdown]"]
|
|
663
|
+
: {};
|
|
664
|
+
const nextSettings = {
|
|
665
|
+
...existingSettings,
|
|
666
|
+
"[markdown]": {
|
|
667
|
+
...existingMarkdownSettings,
|
|
668
|
+
...proseMarkdownSettings
|
|
669
|
+
},
|
|
670
|
+
"markdown.preview.fontFamily": PROSE_MARKDOWN_EDITOR_SETTINGS["markdown.preview.fontFamily"]
|
|
671
|
+
};
|
|
672
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf8");
|
|
673
|
+
return settingsPath;
|
|
674
|
+
}
|
|
527
675
|
function writeInitRootPackageJson(targetRoot) {
|
|
528
676
|
const cliPackage = readJson(path.join(packageRoot, "package.json"));
|
|
529
677
|
const cliVersion = typeof cliPackage.version === "string" ? cliPackage.version : "0.1.0";
|
|
@@ -565,7 +713,7 @@ function listProjects() {
|
|
|
565
713
|
console.log(`- ${id}`);
|
|
566
714
|
}
|
|
567
715
|
}
|
|
568
|
-
function createProject(projectIdOption, titleOption) {
|
|
716
|
+
async function createProject(projectIdOption, titleOption) {
|
|
569
717
|
const projectId = (projectIdOption || "").trim();
|
|
570
718
|
if (!projectId) {
|
|
571
719
|
throw new Error("Project id is required. Use --project <project-id>.");
|
|
@@ -625,11 +773,19 @@ function createProject(projectIdOption, titleOption) {
|
|
|
625
773
|
fs.writeFileSync(charactersNotesPath, "# Characters\n\n", "utf8");
|
|
626
774
|
const projectExtensionsPath = path.join(projectRoot, ".vscode", "extensions.json");
|
|
627
775
|
ensureProjectExtensionsRecommendations(projectRoot);
|
|
776
|
+
let projectSettingsPath = null;
|
|
777
|
+
const enableProseFont = await promptYesNo(PROSE_FONT_PROMPT, true);
|
|
778
|
+
if (enableProseFont) {
|
|
779
|
+
projectSettingsPath = writeProseEditorSettingsForProject(projectRoot);
|
|
780
|
+
}
|
|
628
781
|
logLine(`Created project: ${path.relative(repoRoot, projectRoot)}`);
|
|
629
782
|
logLine(`- ${path.relative(repoRoot, projectJsonPath)}`);
|
|
630
783
|
logLine(`- ${path.relative(repoRoot, projectPackagePath)}`);
|
|
631
784
|
logLine(`- ${path.relative(repoRoot, charactersNotesPath)}`);
|
|
632
785
|
logLine(`- ${path.relative(repoRoot, projectExtensionsPath)}`);
|
|
786
|
+
if (projectSettingsPath) {
|
|
787
|
+
logLine(`- ${path.relative(repoRoot, projectSettingsPath)}`);
|
|
788
|
+
}
|
|
633
789
|
}
|
|
634
790
|
function getProjectIds() {
|
|
635
791
|
const projectsDir = path.join(repoRoot, config.projectsDir);
|