@life-and-dev/mdsite 0.5.2 → 0.6.0
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 +13 -20
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +52 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/commands.test.js +110 -56
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/init.js +3 -62
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/prepare.js +0 -12
- package/dist/commands/prepare.js.map +1 -1
- package/dist/commands/prepare.test.js +17 -14
- package/dist/commands/prepare.test.js.map +1 -1
- package/dist/commands/workflows.test.js +20 -33
- package/dist/commands/workflows.test.js.map +1 -1
- package/dist/config/default-mdsite-config.js +2 -1
- package/dist/config/default-mdsite-config.js.map +1 -1
- package/dist/config/default-mdsite-config.test.js +2 -1
- package/dist/config/default-mdsite-config.test.js.map +1 -1
- package/dist/config/mdsite-config.d.ts +1 -0
- package/dist/config/mdsite-config.js +4 -1
- package/dist/config/mdsite-config.js.map +1 -1
- package/dist/config/mdsite-config.test.js +22 -2
- package/dist/config/mdsite-config.test.js.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +13 -0
- package/dist/index.test.js.map +1 -1
- package/dist/process/child-process.test.js +2 -2
- package/dist/process/child-process.test.js.map +1 -1
- package/dist/process/runtime-state.js +6 -2
- package/dist/process/runtime-state.js.map +1 -1
- package/dist/process/runtime-state.test.js +8 -6
- package/dist/process/runtime-state.test.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.js +3 -15
- package/dist/renderer/mdsite-nuxt.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.test.js +7 -28
- package/dist/renderer/mdsite-nuxt.test.js.map +1 -1
- package/mdsite-nuxt/app/composables/useSiteConfig.test.ts +49 -0
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +17 -3
- package/mdsite-nuxt/nuxt.config.ts +13 -1
- package/mdsite-nuxt/scripts/renderer-hooks.test.ts +0 -5
- package/mdsite-nuxt/scripts/renderer-hooks.ts +2 -3
- package/mdsite-nuxt/scripts/start.test.ts +0 -1
- package/mdsite-nuxt/scripts/start.ts +0 -1
- package/mdsite-nuxt/utils/mdsite-config.ts +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,32 +22,28 @@ You write Markdown. For example, your content directory with a few pages and a l
|
|
|
22
22
|
|
|
23
23
|
```yaml
|
|
24
24
|
my-docs/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
└── logo.png
|
|
25
|
+
├── index.md
|
|
26
|
+
├── about.md
|
|
27
|
+
├── blog/
|
|
28
|
+
│ ├── 2026-01-hello.md
|
|
29
|
+
│ └── 2026-03-release.md
|
|
30
|
+
└── logo.png
|
|
32
31
|
```
|
|
33
32
|
|
|
34
33
|
Run the `mdsite static` in your repo to generate the static pages:
|
|
35
34
|
|
|
36
35
|
```yaml
|
|
37
36
|
my-docs/
|
|
38
|
-
├──
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
│ ├──
|
|
42
|
-
│
|
|
43
|
-
|
|
44
|
-
│ └── logo.png
|
|
37
|
+
├── index.md
|
|
38
|
+
├── about.md
|
|
39
|
+
├── blog/
|
|
40
|
+
│ ├── 2026-01-hello.md
|
|
41
|
+
│ └── 2026-03-release.md
|
|
42
|
+
├── logo.png
|
|
45
43
|
├── mdsite.yml # site configuration
|
|
46
|
-
├── package.json # package configuration
|
|
47
|
-
├── package-lock.json # package lock
|
|
48
44
|
├── .mdsite/ # renderer working dir (gitignored)
|
|
49
45
|
│ ├── mdsite.log # detached webserver logs
|
|
50
|
-
│ └── ... # Other Nuxt render files
|
|
46
|
+
│ └── ... # Other Node and Nuxt render files
|
|
51
47
|
└── .output/ # deployable static site
|
|
52
48
|
└── public/
|
|
53
49
|
├── index.html
|
|
@@ -63,9 +59,6 @@ my-docs/
|
|
|
63
59
|
└── ...
|
|
64
60
|
```
|
|
65
61
|
|
|
66
|
-
> [!NOTE]
|
|
67
|
-
> You can technically mix your content and project files in the same directory, but it's easier to maintain content and generated files separately.
|
|
68
|
-
|
|
69
62
|
## Install
|
|
70
63
|
|
|
71
64
|
Install the CLI globally from the npm registry on any machine with Node.js (>= 24.0.0) and npm:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCleanCommand(contentDir: string): Promise<string>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { access, rm } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadMdsiteConfig } from '../config/mdsite-config.js';
|
|
4
|
+
import { isProcessRunning, readRuntimeState } from '../process/runtime-state.js';
|
|
5
|
+
async function pathExists(targetPath) {
|
|
6
|
+
try {
|
|
7
|
+
await access(targetPath);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function runCleanCommand(contentDir) {
|
|
15
|
+
const loaded = await loadMdsiteConfig(contentDir);
|
|
16
|
+
const { config, configDir } = loaded;
|
|
17
|
+
// Refuse to wipe state while a tracked process is still pointing at it.
|
|
18
|
+
// Tracked PIDs live inside <server.path>, so the clean would orphan them.
|
|
19
|
+
const trackedStates = await Promise.all([
|
|
20
|
+
readRuntimeState(configDir, config, 'start'),
|
|
21
|
+
readRuntimeState(configDir, config, 'preview')
|
|
22
|
+
]);
|
|
23
|
+
for (const state of trackedStates) {
|
|
24
|
+
if (state && isProcessRunning(state.pid)) {
|
|
25
|
+
const alias = state.kind === 'start' ? 'live' : 'static';
|
|
26
|
+
throw new Error(`mdsite ${alias} is running with PID ${state.pid}. Run \`mdsite stop\` before \`mdsite clean\`.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const rendererPath = path.resolve(configDir, config.server.path);
|
|
30
|
+
const outputPath = path.resolve(configDir, config.server.output);
|
|
31
|
+
const [rendererExists, outputExists] = await Promise.all([
|
|
32
|
+
pathExists(rendererPath),
|
|
33
|
+
pathExists(outputPath)
|
|
34
|
+
]);
|
|
35
|
+
// `force: true` lets us skip non-existent paths without a separate branch.
|
|
36
|
+
await Promise.all([
|
|
37
|
+
rm(rendererPath, { recursive: true, force: true }),
|
|
38
|
+
rm(outputPath, { recursive: true, force: true })
|
|
39
|
+
]);
|
|
40
|
+
const removed = [];
|
|
41
|
+
if (rendererExists) {
|
|
42
|
+
removed.push(config.server.path);
|
|
43
|
+
}
|
|
44
|
+
if (outputExists) {
|
|
45
|
+
removed.push(config.server.output);
|
|
46
|
+
}
|
|
47
|
+
if (removed.length === 0) {
|
|
48
|
+
return `Nothing to clean in ${contentDir}.`;
|
|
49
|
+
}
|
|
50
|
+
return `Removed ${removed.join(' and ')} from ${contentDir}.`;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAEhF,KAAK,UAAU,UAAU,CAAC,UAAkB;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB;IACtD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAA;IACjD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAA;IAEpC,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtC,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;QAC5C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC;KAC/C,CAAC,CAAA;IAEF,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,IAAI,KAAK,IAAI,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;YACxD,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,wBAAwB,KAAK,CAAC,GAAG,gDAAgD,CAAC,CAAA;QACnH,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAEhE,MAAM,CAAC,cAAc,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACvD,UAAU,CAAC,YAAY,CAAC;QACxB,UAAU,CAAC,UAAU,CAAC;KACvB,CAAC,CAAA;IAEF,2EAA2E;IAC3E,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,EAAE,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAClD,EAAE,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KACjD,CAAC,CAAA;IAEF,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IACD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,uBAAuB,UAAU,GAAG,CAAA;IAC7C,CAAC;IAED,OAAO,WAAW,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,UAAU,GAAG,CAAA;AAC/D,CAAC"}
|
|
@@ -3,7 +3,6 @@ vi.mock('node:fs/promises', () => ({
|
|
|
3
3
|
access: vi.fn(),
|
|
4
4
|
copyFile: vi.fn(),
|
|
5
5
|
cp: vi.fn(),
|
|
6
|
-
mkdir: vi.fn(),
|
|
7
6
|
readFile: vi.fn(),
|
|
8
7
|
rm: vi.fn(),
|
|
9
8
|
writeFile: vi.fn()
|
|
@@ -40,11 +39,12 @@ vi.mock('../renderer/mdsite-nuxt.js', () => ({
|
|
|
40
39
|
startRendererInBackground: vi.fn()
|
|
41
40
|
}));
|
|
42
41
|
import path from 'node:path';
|
|
43
|
-
import { access, copyFile, cp,
|
|
42
|
+
import { access, copyFile, cp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
44
43
|
import { buildDefaultMdsiteConfig, loadMdsiteConfig, resolveContentOutputPath, serializeMdsiteConfig } from '../config/mdsite-config.js';
|
|
45
44
|
import { openUrlInBrowser, stopProcess, waitForTcpPort } from '../process/child-process.js';
|
|
46
45
|
import { clearRuntimeState, getRuntimeLogPath, isProcessRunning, readRuntimeState, writeRuntimeState } from '../process/runtime-state.js';
|
|
47
46
|
import { ensurePreviewArtifacts, ensureRendererDependencies, generateRenderer, getBundledRendererDir, getRendererGeneratedOutputPath, hasPreviewArtifacts, prepareRenderer, previewRendererForeground, previewRendererInBackground, startRendererForeground, startRendererInBackground } from '../renderer/mdsite-nuxt.js';
|
|
47
|
+
import { runCleanCommand } from './clean.js';
|
|
48
48
|
import { runGenerateCommand } from './generate.js';
|
|
49
49
|
import { runInitCommand } from './init.js';
|
|
50
50
|
import { runPreviewCommand } from './preview.js';
|
|
@@ -55,7 +55,6 @@ const cpMock = vi.mocked(cp);
|
|
|
55
55
|
const rmMock = vi.mocked(rm);
|
|
56
56
|
const writeFileMock = vi.mocked(writeFile);
|
|
57
57
|
const copyFileMock = vi.mocked(copyFile);
|
|
58
|
-
const mkdirMock = vi.mocked(mkdir);
|
|
59
58
|
const readFileMock = vi.mocked(readFile);
|
|
60
59
|
const getBundledRendererDirMock = vi.mocked(getBundledRendererDir);
|
|
61
60
|
const buildDefaultConfigMock = vi.mocked(buildDefaultMdsiteConfig);
|
|
@@ -86,7 +85,7 @@ const loadedConfig = {
|
|
|
86
85
|
features: { bibleTooltips: true, sourceEdit: true },
|
|
87
86
|
menu: [],
|
|
88
87
|
footer: [],
|
|
89
|
-
server: { output: '.output', path: '.renderer', repo: 'repo' },
|
|
88
|
+
server: { output: '.output', path: '.renderer', repo: 'repo', gitBranch: 'main' },
|
|
90
89
|
site: { canonical: '', name: 'Docs' },
|
|
91
90
|
themes: { light: { colors: {} }, dark: { colors: {} } }
|
|
92
91
|
},
|
|
@@ -102,7 +101,8 @@ describe('command helpers', () => {
|
|
|
102
101
|
hasPreviewArtifactsMock.mockResolvedValue(true);
|
|
103
102
|
prepareRendererMock.mockResolvedValue({ rendererDir: '/renderer', rendererEnv: { TEST: '1' } });
|
|
104
103
|
getRuntimeLogPathMock.mockImplementation((configDir, config, kind) => {
|
|
105
|
-
|
|
104
|
+
const basename = kind === 'start' ? 'live' : 'static';
|
|
105
|
+
return `${configDir}/${config.server.path}/${basename}.log`;
|
|
106
106
|
});
|
|
107
107
|
resolveOutputMock.mockReturnValue('/content/.output/public');
|
|
108
108
|
getRendererGeneratedOutputPathMock.mockReturnValue('/renderer/.output/public');
|
|
@@ -119,27 +119,16 @@ describe('command helpers', () => {
|
|
|
119
119
|
});
|
|
120
120
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
121
121
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
122
|
-
|
|
123
|
-
readFileMock.mockResolvedValueOnce(JSON.stringify({ name: 'mdsite-nuxt-renderer', version: '0.1.0', lockfileVersion: 3, packages: { '': { name: 'mdsite-nuxt-renderer', version: '0.1.0' } } }));
|
|
124
|
-
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml, .nvmrc, .renderer/package.json, .renderer/package-lock.json in /content.');
|
|
122
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml, .nvmrc in /content.');
|
|
125
123
|
expect(buildDefaultConfigMock).toHaveBeenCalledWith('/content');
|
|
126
124
|
expect(loadConfigMock).not.toHaveBeenCalled();
|
|
127
125
|
expect(writeFileMock).toHaveBeenCalledWith('/content/mdsite.yml', 'serialized-config', 'utf8');
|
|
128
126
|
expect(writeFileMock).toHaveBeenCalledWith('/content/.nvmrc', '24\n', 'utf8');
|
|
129
|
-
expect(mkdirMock).toHaveBeenCalledWith(path.join('/content', '.renderer'), { recursive: true });
|
|
130
|
-
expect(readFileMock).toHaveBeenCalledWith(path.join('/home/gizbar/git/mdsite/mdsite-nuxt', 'package.json'), 'utf8');
|
|
131
|
-
expect(readFileMock).toHaveBeenCalledWith(path.join('/home/gizbar/git/mdsite/mdsite-nuxt', 'package-lock.json'), 'utf8');
|
|
132
|
-
const writtenPkg = writeFileMock.mock.calls.find(([p]) => p === path.join('/content', '.renderer', 'package.json'));
|
|
133
|
-
expect(writtenPkg).toBeDefined();
|
|
134
|
-
expect(writtenPkg[1]).toBe(`${JSON.stringify({ name: 'content', version: '0.1.0', description: 'Docs', scripts: { dev: 'x' } }, null, 2)}\n`);
|
|
135
|
-
const writtenLock = writeFileMock.mock.calls.find(([p]) => p === path.join('/content', '.renderer', 'package-lock.json'));
|
|
136
|
-
expect(writtenLock).toBeDefined();
|
|
137
|
-
expect(writtenLock[1]).toBe(`${JSON.stringify({ name: 'content', version: '0.1.0', lockfileVersion: 3, packages: { '': { name: 'content', version: '0.1.0' } } }, null, 2)}\n`);
|
|
138
127
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
139
128
|
expect(writeFileMock).toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('.renderer/*'), 'utf8');
|
|
140
|
-
expect(writeFileMock).toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package.json'), 'utf8');
|
|
141
|
-
expect(writeFileMock).toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package-lock.json'), 'utf8');
|
|
142
129
|
expect(writeFileMock).toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('.output/'), 'utf8');
|
|
130
|
+
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package.json'), 'utf8');
|
|
131
|
+
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package-lock.json'), 'utf8');
|
|
143
132
|
});
|
|
144
133
|
it('runInitCommand preserves an existing .nvmrc and only creates the rest', async () => {
|
|
145
134
|
const existing = new Set(['/content/.nvmrc']);
|
|
@@ -150,33 +139,23 @@ describe('command helpers', () => {
|
|
|
150
139
|
});
|
|
151
140
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
152
141
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
153
|
-
|
|
154
|
-
readFileMock.mockResolvedValueOnce(JSON.stringify({ name: 'mdsite-nuxt-renderer', version: '0.1.0', lockfileVersion: 3, packages: { '': { name: 'mdsite-nuxt-renderer', version: '0.1.0' } } }));
|
|
155
|
-
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml, .renderer/package.json, .renderer/package-lock.json in /content.');
|
|
142
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml in /content.');
|
|
156
143
|
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.nvmrc', expect.anything(), expect.anything());
|
|
157
144
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
158
145
|
});
|
|
159
146
|
it('runInitCommand loads an existing mdsite.yml to repair missing files without overwriting it', async () => {
|
|
160
147
|
const existing = new Set([
|
|
161
|
-
'/content/mdsite.yml'
|
|
162
|
-
'/content/.nvmrc',
|
|
163
|
-
path.join('/content', '.renderer', 'package.json')
|
|
148
|
+
'/content/mdsite.yml'
|
|
164
149
|
]);
|
|
165
150
|
accessMock.mockImplementation(async (p) => {
|
|
166
151
|
if (typeof p === 'string' && existing.has(p))
|
|
167
152
|
return;
|
|
168
153
|
throw new Error('missing');
|
|
169
154
|
});
|
|
170
|
-
|
|
171
|
-
await expect(runInitCommand('/content')).resolves.toBe('Created .renderer/package-lock.json in /content.');
|
|
155
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created .nvmrc in /content.');
|
|
172
156
|
expect(loadConfigMock).toHaveBeenCalledWith('/content');
|
|
173
157
|
expect(buildDefaultConfigMock).not.toHaveBeenCalled();
|
|
174
158
|
expect(writeFileMock).not.toHaveBeenCalledWith('/content/mdsite.yml', expect.anything(), expect.anything());
|
|
175
|
-
const writeFileCallsForPkg = writeFileMock.mock.calls.filter(([p]) => typeof p === 'string' && p === path.join('/content', '.renderer', 'package.json'));
|
|
176
|
-
expect(writeFileCallsForPkg).toHaveLength(0);
|
|
177
|
-
const writtenLock = writeFileMock.mock.calls.find(([p]) => p === path.join('/content', '.renderer', 'package-lock.json'));
|
|
178
|
-
expect(writtenLock).toBeDefined();
|
|
179
|
-
expect(writtenLock[1]).toBe(`${JSON.stringify({ name: 'content', version: '0.1.0', lockfileVersion: 3, packages: { '': { name: 'content', version: '0.1.0' } } }, null, 2)}\n`);
|
|
180
159
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
181
160
|
});
|
|
182
161
|
it('runInitCommand reports nothing to create when every file already exists', async () => {
|
|
@@ -218,7 +197,7 @@ describe('command helpers', () => {
|
|
|
218
197
|
readRuntimeStateMock.mockResolvedValueOnce({ kind: 'start', pid: 44 });
|
|
219
198
|
isProcessRunningMock.mockReturnValueOnce(false);
|
|
220
199
|
startRendererInBackgroundMock.mockResolvedValueOnce(777);
|
|
221
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 777). Log: /content/.renderer/
|
|
200
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 777). Log: /content/.renderer/live.log');
|
|
222
201
|
expect(ensureRendererDependenciesMock).toHaveBeenCalledWith('/renderer');
|
|
223
202
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
224
203
|
expect(waitForTcpPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
@@ -239,7 +218,7 @@ describe('command helpers', () => {
|
|
|
239
218
|
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'start.local', NUXT_PORT: '4321' }
|
|
240
219
|
});
|
|
241
220
|
startRendererInBackgroundMock.mockResolvedValueOnce(778);
|
|
242
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 778). Log: /content/.renderer/
|
|
221
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 778). Log: /content/.renderer/live.log');
|
|
243
222
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('start.local', 4321);
|
|
244
223
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://start.local:4321');
|
|
245
224
|
});
|
|
@@ -247,7 +226,7 @@ describe('command helpers', () => {
|
|
|
247
226
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
248
227
|
startRendererInBackgroundMock.mockResolvedValueOnce(779);
|
|
249
228
|
waitForTcpPortMock.mockResolvedValueOnce(false);
|
|
250
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 779). Log: /content/.renderer/
|
|
229
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 779). Log: /content/.renderer/live.log');
|
|
251
230
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
252
231
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
253
232
|
});
|
|
@@ -255,7 +234,7 @@ describe('command helpers', () => {
|
|
|
255
234
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
256
235
|
startRendererInBackgroundMock.mockResolvedValueOnce(780);
|
|
257
236
|
waitForTcpPortMock.mockRejectedValueOnce(new Error('connect failed'));
|
|
258
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 780). Log: /content/.renderer/
|
|
237
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 780). Log: /content/.renderer/live.log');
|
|
259
238
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
260
239
|
});
|
|
261
240
|
it('runStartCommand exposes the foreground renderer on the network when host is set', async () => {
|
|
@@ -270,12 +249,12 @@ describe('command helpers', () => {
|
|
|
270
249
|
it('runStartCommand binds the detached renderer to a custom host when host is set', async () => {
|
|
271
250
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
272
251
|
startRendererInBackgroundMock.mockResolvedValueOnce(781);
|
|
273
|
-
await expect(runStartCommand('/content', { detached: true, host: '0.0.0.0' })).resolves.toBe('mdsite live running in background (PID 781). Log: /content/.renderer/
|
|
252
|
+
await expect(runStartCommand('/content', { detached: true, host: '0.0.0.0' })).resolves.toBe('mdsite live running in background (PID 781). Log: /content/.renderer/live.log');
|
|
274
253
|
expect(startRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
275
254
|
NUXT_HOST: '0.0.0.0',
|
|
276
255
|
HOST: '0.0.0.0',
|
|
277
256
|
NITRO_HOST: '0.0.0.0'
|
|
278
|
-
}), '/content/.renderer/
|
|
257
|
+
}), '/content/.renderer/live.log');
|
|
279
258
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('0.0.0.0', 3000);
|
|
280
259
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://0.0.0.0:3000');
|
|
281
260
|
});
|
|
@@ -309,7 +288,7 @@ describe('command helpers', () => {
|
|
|
309
288
|
vi.setSystemTime(new Date('2026-04-10T13:00:00.000Z'));
|
|
310
289
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
311
290
|
previewRendererInBackgroundMock.mockResolvedValueOnce(888);
|
|
312
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 888). URL: http://localhost:3000 Log: /content/.renderer/
|
|
291
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 888). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
313
292
|
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
314
293
|
expect(ensurePreviewArtifactsMock.mock.invocationCallOrder[0]).toBeGreaterThan(ensureRendererDependenciesMock.mock.invocationCallOrder[0] ?? 0);
|
|
315
294
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
@@ -320,7 +299,7 @@ describe('command helpers', () => {
|
|
|
320
299
|
PORT: '3000',
|
|
321
300
|
NITRO_HOST: 'localhost',
|
|
322
301
|
NITRO_PORT: '3000'
|
|
323
|
-
}), '/content/.renderer/
|
|
302
|
+
}), '/content/.renderer/static.log');
|
|
324
303
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
325
304
|
expect(waitForTcpPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
326
305
|
expect(openUrlInBrowserMock.mock.invocationCallOrder[0]).toBeGreaterThan(waitForTcpPortMock.mock.invocationCallOrder[0] ?? 0);
|
|
@@ -339,7 +318,7 @@ describe('command helpers', () => {
|
|
|
339
318
|
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' }
|
|
340
319
|
});
|
|
341
320
|
previewRendererInBackgroundMock.mockResolvedValueOnce(999);
|
|
342
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 999). URL: http://preview.local:4321 Log: /content/.renderer/
|
|
321
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 999). URL: http://preview.local:4321 Log: /content/.renderer/static.log');
|
|
343
322
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
344
323
|
HOST: 'preview.local',
|
|
345
324
|
PORT: '4321',
|
|
@@ -347,7 +326,7 @@ describe('command helpers', () => {
|
|
|
347
326
|
NUXT_PORT: '4321',
|
|
348
327
|
NITRO_HOST: 'preview.local',
|
|
349
328
|
NITRO_PORT: '4321'
|
|
350
|
-
}), '/content/.renderer/
|
|
329
|
+
}), '/content/.renderer/static.log');
|
|
351
330
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('preview.local', 4321);
|
|
352
331
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://preview.local:4321');
|
|
353
332
|
});
|
|
@@ -358,7 +337,7 @@ describe('command helpers', () => {
|
|
|
358
337
|
rendererEnv: { HOST: '127.0.0.1', PORT: '4173' }
|
|
359
338
|
});
|
|
360
339
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1000);
|
|
361
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1000). URL: http://127.0.0.1:4173 Log: /content/.renderer/
|
|
340
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1000). URL: http://127.0.0.1:4173 Log: /content/.renderer/static.log');
|
|
362
341
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
363
342
|
HOST: '127.0.0.1',
|
|
364
343
|
PORT: '4173',
|
|
@@ -366,7 +345,7 @@ describe('command helpers', () => {
|
|
|
366
345
|
NUXT_PORT: '4173',
|
|
367
346
|
NITRO_HOST: '127.0.0.1',
|
|
368
347
|
NITRO_PORT: '4173'
|
|
369
|
-
}), '/content/.renderer/
|
|
348
|
+
}), '/content/.renderer/static.log');
|
|
370
349
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('127.0.0.1', 4173);
|
|
371
350
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://127.0.0.1:4173');
|
|
372
351
|
});
|
|
@@ -385,13 +364,13 @@ describe('command helpers', () => {
|
|
|
385
364
|
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' }
|
|
386
365
|
});
|
|
387
366
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1003);
|
|
388
|
-
await expect(runPreviewCommand('/content', { detached: true, host: '0.0.0.0' })).resolves.toBe('mdsite static running in background (PID 1003). URL: http://0.0.0.0:4321 Log: /content/.renderer/
|
|
367
|
+
await expect(runPreviewCommand('/content', { detached: true, host: '0.0.0.0' })).resolves.toBe('mdsite static running in background (PID 1003). URL: http://0.0.0.0:4321 Log: /content/.renderer/static.log');
|
|
389
368
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
390
369
|
NUXT_HOST: '0.0.0.0',
|
|
391
370
|
HOST: '0.0.0.0',
|
|
392
371
|
NITRO_HOST: '0.0.0.0',
|
|
393
372
|
NUXT_PORT: '4321'
|
|
394
|
-
}), '/content/.renderer/
|
|
373
|
+
}), '/content/.renderer/static.log');
|
|
395
374
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('0.0.0.0', 4321);
|
|
396
375
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://0.0.0.0:4321');
|
|
397
376
|
});
|
|
@@ -399,7 +378,7 @@ describe('command helpers', () => {
|
|
|
399
378
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
400
379
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1001);
|
|
401
380
|
waitForTcpPortMock.mockResolvedValueOnce(false);
|
|
402
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1001). URL: http://localhost:3000 Log: /content/.renderer/
|
|
381
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1001). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
403
382
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
404
383
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
405
384
|
});
|
|
@@ -407,7 +386,7 @@ describe('command helpers', () => {
|
|
|
407
386
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
408
387
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1002);
|
|
409
388
|
waitForTcpPortMock.mockRejectedValueOnce(new Error('connect failed'));
|
|
410
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1002). URL: http://localhost:3000 Log: /content/.renderer/
|
|
389
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1002). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
411
390
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
412
391
|
});
|
|
413
392
|
it('runStartCommand auto-runs mdsite init when mdsite.yml is missing', async () => {
|
|
@@ -416,7 +395,7 @@ describe('command helpers', () => {
|
|
|
416
395
|
});
|
|
417
396
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
418
397
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
419
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
398
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
420
399
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
421
400
|
await expect(runStartCommand('/content')).resolves.toBeUndefined();
|
|
422
401
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -431,7 +410,7 @@ describe('command helpers', () => {
|
|
|
431
410
|
});
|
|
432
411
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
433
412
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
434
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
413
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
435
414
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
436
415
|
await expect(runPreviewCommand('/content')).resolves.toBeUndefined();
|
|
437
416
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -447,13 +426,13 @@ describe('command helpers', () => {
|
|
|
447
426
|
});
|
|
448
427
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
449
428
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
450
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
429
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
451
430
|
previewRendererInBackgroundMock.mockResolvedValueOnce(5555);
|
|
452
431
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
453
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 5555). URL: http://localhost:3000 Log: /content/.renderer/
|
|
432
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 5555). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
454
433
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
455
434
|
expect(writeFileMock).toHaveBeenCalledWith('/content/mdsite.yml', 'serialized-config', 'utf8');
|
|
456
|
-
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/
|
|
435
|
+
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/static.log');
|
|
457
436
|
consoleSpy.mockRestore();
|
|
458
437
|
});
|
|
459
438
|
it('runGenerateCommand auto-runs mdsite init when mdsite.yml is missing', async () => {
|
|
@@ -462,7 +441,7 @@ describe('command helpers', () => {
|
|
|
462
441
|
});
|
|
463
442
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
464
443
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
465
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
444
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
466
445
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
467
446
|
await expect(runGenerateCommand('/content')).resolves.toBe('Generated site synced to /content/.output/public');
|
|
468
447
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -487,10 +466,10 @@ describe('command helpers', () => {
|
|
|
487
466
|
hasPreviewArtifactsMock.mockResolvedValueOnce(false);
|
|
488
467
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1234);
|
|
489
468
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
490
|
-
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1234). URL: http://localhost:3000 Log: /content/.renderer/
|
|
469
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1234). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
491
470
|
expect(hasPreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
492
471
|
expect(generateRendererMock).toHaveBeenCalledWith('/renderer', { TEST: '1' });
|
|
493
|
-
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/
|
|
472
|
+
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/static.log');
|
|
494
473
|
consoleSpy.mockRestore();
|
|
495
474
|
});
|
|
496
475
|
it('runGenerateCommand syncs renderer output to the configured destination', async () => {
|
|
@@ -514,5 +493,80 @@ describe('command helpers', () => {
|
|
|
514
493
|
expect(clearRuntimeStateMock).toHaveBeenNthCalledWith(1, '/content', loadedConfig.config, 'start');
|
|
515
494
|
expect(clearRuntimeStateMock).toHaveBeenNthCalledWith(2, '/content', loadedConfig.config, 'preview');
|
|
516
495
|
});
|
|
496
|
+
it('runCleanCommand removes both configured working dirs and reports both removals', async () => {
|
|
497
|
+
// `.renderer` and `.output` are reported as existing; everything else throws on access().
|
|
498
|
+
const existing = new Set(['/content/.renderer', '/content/.output']);
|
|
499
|
+
accessMock.mockImplementation(async (p) => {
|
|
500
|
+
if (typeof p === 'string' && existing.has(p))
|
|
501
|
+
return;
|
|
502
|
+
throw new Error('missing');
|
|
503
|
+
});
|
|
504
|
+
// No active tracked start or preview processes.
|
|
505
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
506
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .renderer and .output from /content.');
|
|
507
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
508
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
509
|
+
});
|
|
510
|
+
it('runCleanCommand reports the single existing directory when only one of the two is present', async () => {
|
|
511
|
+
const existing = new Set(['/content/.output']);
|
|
512
|
+
accessMock.mockImplementation(async (p) => {
|
|
513
|
+
if (typeof p === 'string' && existing.has(p))
|
|
514
|
+
return;
|
|
515
|
+
throw new Error('missing');
|
|
516
|
+
});
|
|
517
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
518
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .output from /content.');
|
|
519
|
+
expect(rmMock).toHaveBeenCalledTimes(2);
|
|
520
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
521
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
522
|
+
});
|
|
523
|
+
it('runCleanCommand is a no-op when neither configured working dir exists', async () => {
|
|
524
|
+
const existing = new Set();
|
|
525
|
+
accessMock.mockImplementation(async (p) => {
|
|
526
|
+
if (typeof p === 'string' && existing.has(p))
|
|
527
|
+
return;
|
|
528
|
+
throw new Error('missing');
|
|
529
|
+
});
|
|
530
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
531
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Nothing to clean in /content.');
|
|
532
|
+
// rm() is still called on both paths so force:true silently ignores missing dirs.
|
|
533
|
+
expect(rmMock).toHaveBeenCalledTimes(2);
|
|
534
|
+
});
|
|
535
|
+
it('runCleanCommand refuses to delete the working dirs while a tracked start process is alive', async () => {
|
|
536
|
+
readRuntimeStateMock
|
|
537
|
+
.mockResolvedValueOnce({ kind: 'start', pid: 44 })
|
|
538
|
+
.mockResolvedValueOnce(null);
|
|
539
|
+
isProcessRunningMock.mockReturnValueOnce(true);
|
|
540
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('mdsite live is running with PID 44. Run `mdsite stop` before `mdsite clean`.');
|
|
541
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
542
|
+
});
|
|
543
|
+
it('runCleanCommand refuses to delete the working dirs while a tracked preview process is alive', async () => {
|
|
544
|
+
readRuntimeStateMock
|
|
545
|
+
.mockResolvedValueOnce(null)
|
|
546
|
+
.mockResolvedValueOnce({ kind: 'preview', pid: 55 });
|
|
547
|
+
isProcessRunningMock.mockReturnValueOnce(true);
|
|
548
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('mdsite static is running with PID 55. Run `mdsite stop` before `mdsite clean`.');
|
|
549
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
550
|
+
});
|
|
551
|
+
it('runCleanCommand ignores stale tracked state whose PID is no longer alive', async () => {
|
|
552
|
+
const existing = new Set(['/content/.renderer', '/content/.output']);
|
|
553
|
+
accessMock.mockImplementation(async (p) => {
|
|
554
|
+
if (typeof p === 'string' && existing.has(p))
|
|
555
|
+
return;
|
|
556
|
+
throw new Error('missing');
|
|
557
|
+
});
|
|
558
|
+
readRuntimeStateMock
|
|
559
|
+
.mockResolvedValueOnce({ kind: 'start', pid: 44 })
|
|
560
|
+
.mockResolvedValueOnce({ kind: 'preview', pid: 55 });
|
|
561
|
+
isProcessRunningMock.mockReturnValue(false);
|
|
562
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .renderer and .output from /content.');
|
|
563
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
564
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
565
|
+
});
|
|
566
|
+
it('runCleanCommand surfaces the missing-config error from loadMdsiteConfig', async () => {
|
|
567
|
+
loadConfigMock.mockRejectedValueOnce(new Error('Missing mdsite.yml in /content. Run `mdsite init` first.'));
|
|
568
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('Missing mdsite.yml in /content. Run `mdsite init` first.');
|
|
569
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
570
|
+
});
|
|
517
571
|
});
|
|
518
572
|
//# sourceMappingURL=commands.test.js.map
|