@life-and-dev/mdsite 0.5.3 → 0.7.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 +29 -37
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +70 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/commands.test.js +157 -75
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/generate.js +5 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.js +5 -64
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/prepare.js +2 -14
- package/dist/commands/prepare.js.map +1 -1
- package/dist/commands/prepare.test.js +26 -24
- package/dist/commands/prepare.test.js.map +1 -1
- package/dist/commands/preview.js +21 -21
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/start.js +13 -11
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.js +7 -4
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/workflows.test.js +42 -56
- package/dist/commands/workflows.test.js.map +1 -1
- package/dist/config/default-mdsite-config.js +7 -8
- package/dist/config/default-mdsite-config.js.map +1 -1
- package/dist/config/default-mdsite-config.test.js +7 -8
- package/dist/config/default-mdsite-config.test.js.map +1 -1
- package/dist/config/mdsite-config.d.ts +46 -10
- package/dist/config/mdsite-config.js +46 -24
- package/dist/config/mdsite-config.js.map +1 -1
- package/dist/config/mdsite-config.test.js +55 -50
- 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.d.ts +4 -0
- package/dist/process/child-process.js +33 -1
- package/dist/process/child-process.js.map +1 -1
- package/dist/process/child-process.test.js +41 -5
- package/dist/process/child-process.test.js.map +1 -1
- package/dist/process/runtime-state.d.ts +13 -5
- package/dist/process/runtime-state.js +25 -13
- package/dist/process/runtime-state.js.map +1 -1
- package/dist/process/runtime-state.test.js +10 -10
- package/dist/process/runtime-state.test.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.d.ts +28 -3
- package/dist/renderer/mdsite-nuxt.js +32 -27
- package/dist/renderer/mdsite-nuxt.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.test.js +40 -39
- package/dist/renderer/mdsite-nuxt.test.js.map +1 -1
- package/mdsite-nuxt/app/components/AppFooter.vue +84 -22
- package/mdsite-nuxt/app/composables/useFooter.test.ts +54 -0
- package/mdsite-nuxt/app/composables/useFooter.ts +48 -31
- package/mdsite-nuxt/app/composables/useSiteConfig.test.ts +13 -87
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +7 -26
- package/mdsite-nuxt/app/composables/useSourceEdit.test.ts +103 -0
- package/mdsite-nuxt/app/composables/useSourceEdit.ts +39 -51
- package/mdsite-nuxt/app/layouts/default.vue +10 -3
- package/mdsite-nuxt/nuxt.config.ts +22 -15
- package/mdsite-nuxt/scripts/generate-favicons.test.ts +3 -3
- package/mdsite-nuxt/scripts/generate-favicons.ts +4 -4
- package/mdsite-nuxt/scripts/generate-indices.test.ts +71 -10
- package/mdsite-nuxt/scripts/generate-indices.ts +161 -27
- package/mdsite-nuxt/scripts/renderer-hooks.test.ts +0 -91
- package/mdsite-nuxt/scripts/renderer-hooks.ts +1 -50
- package/mdsite-nuxt/scripts/start.test.ts +0 -1
- package/mdsite-nuxt/scripts/start.ts +0 -1
- package/mdsite-nuxt/utils/mdsite-config.ts +86 -41
- package/package.json +1 -1
- package/mdsite-nuxt/example.config.yml +0 -67
|
@@ -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()
|
|
@@ -24,6 +23,7 @@ vi.mock('../process/runtime-state.js', () => ({
|
|
|
24
23
|
vi.mock('../process/child-process.js', () => ({
|
|
25
24
|
openUrlInBrowser: vi.fn(),
|
|
26
25
|
stopProcess: vi.fn(),
|
|
26
|
+
waitForRendererPort: vi.fn(),
|
|
27
27
|
waitForTcpPort: vi.fn()
|
|
28
28
|
}));
|
|
29
29
|
vi.mock('../renderer/mdsite-nuxt.js', () => ({
|
|
@@ -31,7 +31,6 @@ vi.mock('../renderer/mdsite-nuxt.js', () => ({
|
|
|
31
31
|
ensureRendererDependencies: vi.fn(),
|
|
32
32
|
generateRenderer: vi.fn(),
|
|
33
33
|
getBundledRendererDir: vi.fn(),
|
|
34
|
-
getRendererGeneratedOutputPath: vi.fn(),
|
|
35
34
|
hasPreviewArtifacts: vi.fn(),
|
|
36
35
|
prepareRenderer: vi.fn(),
|
|
37
36
|
previewRendererForeground: 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
|
-
import { openUrlInBrowser, stopProcess, waitForTcpPort } from '../process/child-process.js';
|
|
44
|
+
import { openUrlInBrowser, stopProcess, waitForRendererPort, waitForTcpPort } from '../process/child-process.js';
|
|
46
45
|
import { clearRuntimeState, getRuntimeLogPath, isProcessRunning, readRuntimeState, writeRuntimeState } from '../process/runtime-state.js';
|
|
47
|
-
import { ensurePreviewArtifacts, ensureRendererDependencies, generateRenderer, getBundledRendererDir,
|
|
46
|
+
import { ensurePreviewArtifacts, ensureRendererDependencies, generateRenderer, getBundledRendererDir, 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);
|
|
@@ -71,7 +70,6 @@ const ensurePreviewArtifactsMock = vi.mocked(ensurePreviewArtifacts);
|
|
|
71
70
|
const hasPreviewArtifactsMock = vi.mocked(hasPreviewArtifacts);
|
|
72
71
|
const ensureRendererDependenciesMock = vi.mocked(ensureRendererDependencies);
|
|
73
72
|
const generateRendererMock = vi.mocked(generateRenderer);
|
|
74
|
-
const getRendererGeneratedOutputPathMock = vi.mocked(getRendererGeneratedOutputPath);
|
|
75
73
|
const prepareRendererMock = vi.mocked(prepareRenderer);
|
|
76
74
|
const previewRendererForegroundMock = vi.mocked(previewRendererForeground);
|
|
77
75
|
const previewRendererInBackgroundMock = vi.mocked(previewRendererInBackground);
|
|
@@ -80,14 +78,13 @@ const startRendererInBackgroundMock = vi.mocked(startRendererInBackground);
|
|
|
80
78
|
const stopProcessMock = vi.mocked(stopProcess);
|
|
81
79
|
const openUrlInBrowserMock = vi.mocked(openUrlInBrowser);
|
|
82
80
|
const waitForTcpPortMock = vi.mocked(waitForTcpPort);
|
|
81
|
+
const waitForRendererPortMock = vi.mocked(waitForRendererPort);
|
|
83
82
|
const loadedConfig = {
|
|
84
83
|
config: {
|
|
85
|
-
|
|
86
|
-
features: { bibleTooltips: true, sourceEdit: true },
|
|
84
|
+
features: { bibleTooltips: true, sourceEdit: '', footer: [] },
|
|
87
85
|
menu: [],
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
site: { canonical: '', name: 'Docs' },
|
|
86
|
+
paths: { input: '', build: '.renderer', output: '.output' },
|
|
87
|
+
site: { canonical: '', favicon: '', name: 'Docs' },
|
|
91
88
|
themes: { light: { colors: {} }, dark: { colors: {} } }
|
|
92
89
|
},
|
|
93
90
|
configDir: '/content',
|
|
@@ -100,15 +97,20 @@ describe('command helpers', () => {
|
|
|
100
97
|
vi.useRealTimers();
|
|
101
98
|
loadConfigMock.mockResolvedValue(loadedConfig);
|
|
102
99
|
hasPreviewArtifactsMock.mockResolvedValue(true);
|
|
103
|
-
prepareRendererMock.mockResolvedValue({
|
|
100
|
+
prepareRendererMock.mockResolvedValue({
|
|
101
|
+
rendererDir: '/renderer',
|
|
102
|
+
rendererEnv: { TEST: '1' },
|
|
103
|
+
rendererOutputDir: '/renderer/.output'
|
|
104
|
+
});
|
|
104
105
|
getRuntimeLogPathMock.mockImplementation((configDir, config, kind) => {
|
|
105
|
-
|
|
106
|
+
const basename = kind === 'start' ? 'live' : 'static';
|
|
107
|
+
return `${configDir}/${config.paths.build}/${basename}.log`;
|
|
106
108
|
});
|
|
107
109
|
resolveOutputMock.mockReturnValue('/content/.output/public');
|
|
108
|
-
getRendererGeneratedOutputPathMock.mockReturnValue('/renderer/.output/public');
|
|
109
110
|
getBundledRendererDirMock.mockReturnValue('/home/gizbar/git/mdsite/mdsite-nuxt');
|
|
110
111
|
openUrlInBrowserMock.mockResolvedValue(true);
|
|
111
112
|
waitForTcpPortMock.mockResolvedValue(true);
|
|
113
|
+
waitForRendererPortMock.mockImplementation(async (_logPath, fallbackPort) => fallbackPort);
|
|
112
114
|
});
|
|
113
115
|
it('runInitCommand creates every mdsite file when the content dir is empty', async () => {
|
|
114
116
|
const existing = new Set();
|
|
@@ -119,27 +121,16 @@ describe('command helpers', () => {
|
|
|
119
121
|
});
|
|
120
122
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
121
123
|
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.');
|
|
124
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml, .nvmrc in /content.');
|
|
125
125
|
expect(buildDefaultConfigMock).toHaveBeenCalledWith('/content');
|
|
126
126
|
expect(loadConfigMock).not.toHaveBeenCalled();
|
|
127
127
|
expect(writeFileMock).toHaveBeenCalledWith('/content/mdsite.yml', 'serialized-config', 'utf8');
|
|
128
128
|
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
129
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
139
130
|
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
131
|
expect(writeFileMock).toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('.output/'), 'utf8');
|
|
132
|
+
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package.json'), 'utf8');
|
|
133
|
+
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.gitignore', expect.stringContaining('!.renderer/package-lock.json'), 'utf8');
|
|
143
134
|
});
|
|
144
135
|
it('runInitCommand preserves an existing .nvmrc and only creates the rest', async () => {
|
|
145
136
|
const existing = new Set(['/content/.nvmrc']);
|
|
@@ -150,33 +141,23 @@ describe('command helpers', () => {
|
|
|
150
141
|
});
|
|
151
142
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
152
143
|
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.');
|
|
144
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created mdsite.yml in /content.');
|
|
156
145
|
expect(writeFileMock).not.toHaveBeenCalledWith('/content/.nvmrc', expect.anything(), expect.anything());
|
|
157
146
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
158
147
|
});
|
|
159
148
|
it('runInitCommand loads an existing mdsite.yml to repair missing files without overwriting it', async () => {
|
|
160
149
|
const existing = new Set([
|
|
161
|
-
'/content/mdsite.yml'
|
|
162
|
-
'/content/.nvmrc',
|
|
163
|
-
path.join('/content', '.renderer', 'package.json')
|
|
150
|
+
'/content/mdsite.yml'
|
|
164
151
|
]);
|
|
165
152
|
accessMock.mockImplementation(async (p) => {
|
|
166
153
|
if (typeof p === 'string' && existing.has(p))
|
|
167
154
|
return;
|
|
168
155
|
throw new Error('missing');
|
|
169
156
|
});
|
|
170
|
-
|
|
171
|
-
await expect(runInitCommand('/content')).resolves.toBe('Created .renderer/package-lock.json in /content.');
|
|
157
|
+
await expect(runInitCommand('/content')).resolves.toBe('Created .nvmrc in /content.');
|
|
172
158
|
expect(loadConfigMock).toHaveBeenCalledWith('/content');
|
|
173
159
|
expect(buildDefaultConfigMock).not.toHaveBeenCalled();
|
|
174
160
|
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
161
|
expect(copyFileMock).not.toHaveBeenCalled();
|
|
181
162
|
});
|
|
182
163
|
it('runInitCommand reports nothing to create when every file already exists', async () => {
|
|
@@ -218,7 +199,7 @@ describe('command helpers', () => {
|
|
|
218
199
|
readRuntimeStateMock.mockResolvedValueOnce({ kind: 'start', pid: 44 });
|
|
219
200
|
isProcessRunningMock.mockReturnValueOnce(false);
|
|
220
201
|
startRendererInBackgroundMock.mockResolvedValueOnce(777);
|
|
221
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 777). Log: /content/.renderer/
|
|
202
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 777). Log: /content/.renderer/live.log');
|
|
222
203
|
expect(ensureRendererDependenciesMock).toHaveBeenCalledWith('/renderer');
|
|
223
204
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
224
205
|
expect(waitForTcpPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
@@ -236,18 +217,30 @@ describe('command helpers', () => {
|
|
|
236
217
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
237
218
|
prepareRendererMock.mockResolvedValueOnce({
|
|
238
219
|
rendererDir: '/renderer',
|
|
239
|
-
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'start.local', NUXT_PORT: '4321' }
|
|
220
|
+
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'start.local', NUXT_PORT: '4321' },
|
|
221
|
+
rendererOutputDir: '/renderer/.output'
|
|
240
222
|
});
|
|
241
223
|
startRendererInBackgroundMock.mockResolvedValueOnce(778);
|
|
242
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 778). Log: /content/.renderer/
|
|
224
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 778). Log: /content/.renderer/live.log');
|
|
225
|
+
expect(waitForRendererPortMock).toHaveBeenCalledWith('/content/.renderer/live.log', 4321);
|
|
243
226
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('start.local', 4321);
|
|
244
227
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://start.local:4321');
|
|
245
228
|
});
|
|
229
|
+
it('runStartCommand opens the actual port when Nuxt falls back to the next free one in detached mode', async () => {
|
|
230
|
+
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
231
|
+
startRendererInBackgroundMock.mockResolvedValueOnce(7781);
|
|
232
|
+
waitForRendererPortMock.mockResolvedValueOnce(3001);
|
|
233
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 7781). Log: /content/.renderer/live.log');
|
|
234
|
+
expect(waitForRendererPortMock).toHaveBeenCalledWith('/content/.renderer/live.log', 3000);
|
|
235
|
+
expect(waitForRendererPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
236
|
+
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3001);
|
|
237
|
+
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://localhost:3001');
|
|
238
|
+
});
|
|
246
239
|
it('runStartCommand does not open the browser when detached start readiness times out', async () => {
|
|
247
240
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
248
241
|
startRendererInBackgroundMock.mockResolvedValueOnce(779);
|
|
249
242
|
waitForTcpPortMock.mockResolvedValueOnce(false);
|
|
250
|
-
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 779). Log: /content/.renderer/
|
|
243
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 779). Log: /content/.renderer/live.log');
|
|
251
244
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
252
245
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
253
246
|
});
|
|
@@ -255,7 +248,7 @@ describe('command helpers', () => {
|
|
|
255
248
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
256
249
|
startRendererInBackgroundMock.mockResolvedValueOnce(780);
|
|
257
250
|
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/
|
|
251
|
+
await expect(runStartCommand('/content', { detached: true })).resolves.toBe('mdsite live running in background (PID 780). Log: /content/.renderer/live.log');
|
|
259
252
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
260
253
|
});
|
|
261
254
|
it('runStartCommand exposes the foreground renderer on the network when host is set', async () => {
|
|
@@ -270,12 +263,12 @@ describe('command helpers', () => {
|
|
|
270
263
|
it('runStartCommand binds the detached renderer to a custom host when host is set', async () => {
|
|
271
264
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
272
265
|
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/
|
|
266
|
+
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
267
|
expect(startRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
275
268
|
NUXT_HOST: '0.0.0.0',
|
|
276
269
|
HOST: '0.0.0.0',
|
|
277
270
|
NITRO_HOST: '0.0.0.0'
|
|
278
|
-
}), '/content/.renderer/
|
|
271
|
+
}), '/content/.renderer/live.log');
|
|
279
272
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('0.0.0.0', 3000);
|
|
280
273
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://0.0.0.0:3000');
|
|
281
274
|
});
|
|
@@ -288,7 +281,7 @@ describe('command helpers', () => {
|
|
|
288
281
|
expect(waitForTcpPortMock).not.toHaveBeenCalled();
|
|
289
282
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
290
283
|
expect(ensureRendererDependenciesMock).toHaveBeenCalledWith('/renderer');
|
|
291
|
-
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
284
|
+
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer/.output');
|
|
292
285
|
expect(previewRendererForegroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
293
286
|
TEST: '1',
|
|
294
287
|
NUXT_HOST: 'localhost',
|
|
@@ -309,8 +302,8 @@ describe('command helpers', () => {
|
|
|
309
302
|
vi.setSystemTime(new Date('2026-04-10T13:00:00.000Z'));
|
|
310
303
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
311
304
|
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/
|
|
313
|
-
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
305
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 888). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
306
|
+
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer/.output');
|
|
314
307
|
expect(ensurePreviewArtifactsMock.mock.invocationCallOrder[0]).toBeGreaterThan(ensureRendererDependenciesMock.mock.invocationCallOrder[0] ?? 0);
|
|
315
308
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
316
309
|
TEST: '1',
|
|
@@ -320,7 +313,7 @@ describe('command helpers', () => {
|
|
|
320
313
|
PORT: '3000',
|
|
321
314
|
NITRO_HOST: 'localhost',
|
|
322
315
|
NITRO_PORT: '3000'
|
|
323
|
-
}), '/content/.renderer/
|
|
316
|
+
}), '/content/.renderer/static.log');
|
|
324
317
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
325
318
|
expect(waitForTcpPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
326
319
|
expect(openUrlInBrowserMock.mock.invocationCallOrder[0]).toBeGreaterThan(waitForTcpPortMock.mock.invocationCallOrder[0] ?? 0);
|
|
@@ -336,10 +329,11 @@ describe('command helpers', () => {
|
|
|
336
329
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
337
330
|
prepareRendererMock.mockResolvedValueOnce({
|
|
338
331
|
rendererDir: '/renderer',
|
|
339
|
-
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' }
|
|
332
|
+
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' },
|
|
333
|
+
rendererOutputDir: '/renderer/.output'
|
|
340
334
|
});
|
|
341
335
|
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/
|
|
336
|
+
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
337
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
344
338
|
HOST: 'preview.local',
|
|
345
339
|
PORT: '4321',
|
|
@@ -347,18 +341,30 @@ describe('command helpers', () => {
|
|
|
347
341
|
NUXT_PORT: '4321',
|
|
348
342
|
NITRO_HOST: 'preview.local',
|
|
349
343
|
NITRO_PORT: '4321'
|
|
350
|
-
}), '/content/.renderer/
|
|
344
|
+
}), '/content/.renderer/static.log');
|
|
345
|
+
expect(waitForRendererPortMock).toHaveBeenCalledWith('/content/.renderer/static.log', 4321);
|
|
351
346
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('preview.local', 4321);
|
|
352
347
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://preview.local:4321');
|
|
353
348
|
});
|
|
349
|
+
it('runPreviewCommand opens the actual port when Nitro falls back to the next free one in detached mode', async () => {
|
|
350
|
+
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
351
|
+
previewRendererInBackgroundMock.mockResolvedValueOnce(9991);
|
|
352
|
+
waitForRendererPortMock.mockResolvedValueOnce(3001);
|
|
353
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 9991). URL: http://localhost:3001 Log: /content/.renderer/static.log');
|
|
354
|
+
expect(waitForRendererPortMock).toHaveBeenCalledWith('/content/.renderer/static.log', 3000);
|
|
355
|
+
expect(waitForRendererPortMock.mock.invocationCallOrder[0]).toBeGreaterThan(writeRuntimeStateMock.mock.invocationCallOrder[0] ?? 0);
|
|
356
|
+
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3001);
|
|
357
|
+
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://localhost:3001');
|
|
358
|
+
});
|
|
354
359
|
it('runPreviewCommand falls back to HOST and PORT when NUXT preview values are unset in detached mode', async () => {
|
|
355
360
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
356
361
|
prepareRendererMock.mockResolvedValueOnce({
|
|
357
362
|
rendererDir: '/renderer',
|
|
358
|
-
rendererEnv: { HOST: '127.0.0.1', PORT: '4173' }
|
|
363
|
+
rendererEnv: { HOST: '127.0.0.1', PORT: '4173' },
|
|
364
|
+
rendererOutputDir: '/renderer/.output'
|
|
359
365
|
});
|
|
360
366
|
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/
|
|
367
|
+
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
368
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
363
369
|
HOST: '127.0.0.1',
|
|
364
370
|
PORT: '4173',
|
|
@@ -366,7 +372,7 @@ describe('command helpers', () => {
|
|
|
366
372
|
NUXT_PORT: '4173',
|
|
367
373
|
NITRO_HOST: '127.0.0.1',
|
|
368
374
|
NITRO_PORT: '4173'
|
|
369
|
-
}), '/content/.renderer/
|
|
375
|
+
}), '/content/.renderer/static.log');
|
|
370
376
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('127.0.0.1', 4173);
|
|
371
377
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://127.0.0.1:4173');
|
|
372
378
|
});
|
|
@@ -382,16 +388,17 @@ describe('command helpers', () => {
|
|
|
382
388
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
383
389
|
prepareRendererMock.mockResolvedValueOnce({
|
|
384
390
|
rendererDir: '/renderer',
|
|
385
|
-
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' }
|
|
391
|
+
rendererEnv: { HOST: '127.0.0.1', PORT: '4173', NUXT_HOST: 'preview.local', NUXT_PORT: '4321' },
|
|
392
|
+
rendererOutputDir: '/renderer/.output'
|
|
386
393
|
});
|
|
387
394
|
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/
|
|
395
|
+
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
396
|
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({
|
|
390
397
|
NUXT_HOST: '0.0.0.0',
|
|
391
398
|
HOST: '0.0.0.0',
|
|
392
399
|
NITRO_HOST: '0.0.0.0',
|
|
393
400
|
NUXT_PORT: '4321'
|
|
394
|
-
}), '/content/.renderer/
|
|
401
|
+
}), '/content/.renderer/static.log');
|
|
395
402
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('0.0.0.0', 4321);
|
|
396
403
|
expect(openUrlInBrowserMock).toHaveBeenCalledWith('http://0.0.0.0:4321');
|
|
397
404
|
});
|
|
@@ -399,7 +406,7 @@ describe('command helpers', () => {
|
|
|
399
406
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
400
407
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1001);
|
|
401
408
|
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/
|
|
409
|
+
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
410
|
expect(waitForTcpPortMock).toHaveBeenCalledWith('localhost', 3000);
|
|
404
411
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
405
412
|
});
|
|
@@ -407,7 +414,7 @@ describe('command helpers', () => {
|
|
|
407
414
|
readRuntimeStateMock.mockResolvedValueOnce(null);
|
|
408
415
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1002);
|
|
409
416
|
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/
|
|
417
|
+
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
418
|
expect(openUrlInBrowserMock).not.toHaveBeenCalled();
|
|
412
419
|
});
|
|
413
420
|
it('runStartCommand auto-runs mdsite init when mdsite.yml is missing', async () => {
|
|
@@ -416,7 +423,7 @@ describe('command helpers', () => {
|
|
|
416
423
|
});
|
|
417
424
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
418
425
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
419
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
426
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
420
427
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
421
428
|
await expect(runStartCommand('/content')).resolves.toBeUndefined();
|
|
422
429
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -431,7 +438,7 @@ describe('command helpers', () => {
|
|
|
431
438
|
});
|
|
432
439
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
433
440
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
434
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
441
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
435
442
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
436
443
|
await expect(runPreviewCommand('/content')).resolves.toBeUndefined();
|
|
437
444
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -447,13 +454,13 @@ describe('command helpers', () => {
|
|
|
447
454
|
});
|
|
448
455
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
449
456
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
450
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
457
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
451
458
|
previewRendererInBackgroundMock.mockResolvedValueOnce(5555);
|
|
452
459
|
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/
|
|
460
|
+
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
461
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
455
462
|
expect(writeFileMock).toHaveBeenCalledWith('/content/mdsite.yml', 'serialized-config', 'utf8');
|
|
456
|
-
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/
|
|
463
|
+
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/static.log');
|
|
457
464
|
consoleSpy.mockRestore();
|
|
458
465
|
});
|
|
459
466
|
it('runGenerateCommand auto-runs mdsite init when mdsite.yml is missing', async () => {
|
|
@@ -462,7 +469,7 @@ describe('command helpers', () => {
|
|
|
462
469
|
});
|
|
463
470
|
buildDefaultConfigMock.mockResolvedValue(loadedConfig.config);
|
|
464
471
|
serializeConfigMock.mockReturnValue('serialized-config');
|
|
465
|
-
readFileMock.mockResolvedValueOnce('{}')
|
|
472
|
+
readFileMock.mockResolvedValueOnce('{}');
|
|
466
473
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
467
474
|
await expect(runGenerateCommand('/content')).resolves.toBe('Generated site synced to /content/.output/public');
|
|
468
475
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No mdsite.yml found'));
|
|
@@ -475,9 +482,9 @@ describe('command helpers', () => {
|
|
|
475
482
|
hasPreviewArtifactsMock.mockResolvedValueOnce(false);
|
|
476
483
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
477
484
|
await expect(runPreviewCommand('/content')).resolves.toBeUndefined();
|
|
478
|
-
expect(hasPreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
485
|
+
expect(hasPreviewArtifactsMock).toHaveBeenCalledWith('/renderer/.output');
|
|
479
486
|
expect(generateRendererMock).toHaveBeenCalledWith('/renderer', { TEST: '1' });
|
|
480
|
-
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
487
|
+
expect(ensurePreviewArtifactsMock).toHaveBeenCalledWith('/renderer/.output');
|
|
481
488
|
expect(previewRendererForegroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }));
|
|
482
489
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No generated output found'));
|
|
483
490
|
consoleSpy.mockRestore();
|
|
@@ -487,10 +494,10 @@ describe('command helpers', () => {
|
|
|
487
494
|
hasPreviewArtifactsMock.mockResolvedValueOnce(false);
|
|
488
495
|
previewRendererInBackgroundMock.mockResolvedValueOnce(1234);
|
|
489
496
|
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/
|
|
491
|
-
expect(hasPreviewArtifactsMock).toHaveBeenCalledWith('/renderer');
|
|
497
|
+
await expect(runPreviewCommand('/content', { detached: true })).resolves.toBe('mdsite static running in background (PID 1234). URL: http://localhost:3000 Log: /content/.renderer/static.log');
|
|
498
|
+
expect(hasPreviewArtifactsMock).toHaveBeenCalledWith('/renderer/.output');
|
|
492
499
|
expect(generateRendererMock).toHaveBeenCalledWith('/renderer', { TEST: '1' });
|
|
493
|
-
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/
|
|
500
|
+
expect(previewRendererInBackgroundMock).toHaveBeenCalledWith('/renderer', expect.objectContaining({ TEST: '1' }), '/content/.renderer/static.log');
|
|
494
501
|
consoleSpy.mockRestore();
|
|
495
502
|
});
|
|
496
503
|
it('runGenerateCommand syncs renderer output to the configured destination', async () => {
|
|
@@ -514,5 +521,80 @@ describe('command helpers', () => {
|
|
|
514
521
|
expect(clearRuntimeStateMock).toHaveBeenNthCalledWith(1, '/content', loadedConfig.config, 'start');
|
|
515
522
|
expect(clearRuntimeStateMock).toHaveBeenNthCalledWith(2, '/content', loadedConfig.config, 'preview');
|
|
516
523
|
});
|
|
524
|
+
it('runCleanCommand removes both configured working dirs and reports both removals', async () => {
|
|
525
|
+
// `.renderer` and `.output` are reported as existing; everything else throws on access().
|
|
526
|
+
const existing = new Set(['/content/.renderer', '/content/.output']);
|
|
527
|
+
accessMock.mockImplementation(async (p) => {
|
|
528
|
+
if (typeof p === 'string' && existing.has(p))
|
|
529
|
+
return;
|
|
530
|
+
throw new Error('missing');
|
|
531
|
+
});
|
|
532
|
+
// No active tracked start or preview processes.
|
|
533
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
534
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .renderer and .output from /content.');
|
|
535
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
536
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
537
|
+
});
|
|
538
|
+
it('runCleanCommand reports the single existing directory when only one of the two is present', async () => {
|
|
539
|
+
const existing = new Set(['/content/.output']);
|
|
540
|
+
accessMock.mockImplementation(async (p) => {
|
|
541
|
+
if (typeof p === 'string' && existing.has(p))
|
|
542
|
+
return;
|
|
543
|
+
throw new Error('missing');
|
|
544
|
+
});
|
|
545
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
546
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .output from /content.');
|
|
547
|
+
expect(rmMock).toHaveBeenCalledTimes(2);
|
|
548
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
549
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
550
|
+
});
|
|
551
|
+
it('runCleanCommand is a no-op when neither configured working dir exists', async () => {
|
|
552
|
+
const existing = new Set();
|
|
553
|
+
accessMock.mockImplementation(async (p) => {
|
|
554
|
+
if (typeof p === 'string' && existing.has(p))
|
|
555
|
+
return;
|
|
556
|
+
throw new Error('missing');
|
|
557
|
+
});
|
|
558
|
+
readRuntimeStateMock.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
559
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Nothing to clean in /content.');
|
|
560
|
+
// rm() is still called on both paths so force:true silently ignores missing dirs.
|
|
561
|
+
expect(rmMock).toHaveBeenCalledTimes(2);
|
|
562
|
+
});
|
|
563
|
+
it('runCleanCommand refuses to delete the working dirs while a tracked start process is alive', async () => {
|
|
564
|
+
readRuntimeStateMock
|
|
565
|
+
.mockResolvedValueOnce({ kind: 'start', pid: 44 })
|
|
566
|
+
.mockResolvedValueOnce(null);
|
|
567
|
+
isProcessRunningMock.mockReturnValueOnce(true);
|
|
568
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('mdsite live is running with PID 44. Run `mdsite stop` before `mdsite clean`.');
|
|
569
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
570
|
+
});
|
|
571
|
+
it('runCleanCommand refuses to delete the working dirs while a tracked preview process is alive', async () => {
|
|
572
|
+
readRuntimeStateMock
|
|
573
|
+
.mockResolvedValueOnce(null)
|
|
574
|
+
.mockResolvedValueOnce({ kind: 'preview', pid: 55 });
|
|
575
|
+
isProcessRunningMock.mockReturnValueOnce(true);
|
|
576
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('mdsite static is running with PID 55. Run `mdsite stop` before `mdsite clean`.');
|
|
577
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
578
|
+
});
|
|
579
|
+
it('runCleanCommand ignores stale tracked state whose PID is no longer alive', async () => {
|
|
580
|
+
const existing = new Set(['/content/.renderer', '/content/.output']);
|
|
581
|
+
accessMock.mockImplementation(async (p) => {
|
|
582
|
+
if (typeof p === 'string' && existing.has(p))
|
|
583
|
+
return;
|
|
584
|
+
throw new Error('missing');
|
|
585
|
+
});
|
|
586
|
+
readRuntimeStateMock
|
|
587
|
+
.mockResolvedValueOnce({ kind: 'start', pid: 44 })
|
|
588
|
+
.mockResolvedValueOnce({ kind: 'preview', pid: 55 });
|
|
589
|
+
isProcessRunningMock.mockReturnValue(false);
|
|
590
|
+
await expect(runCleanCommand('/content')).resolves.toBe('Removed .renderer and .output from /content.');
|
|
591
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.renderer', { recursive: true, force: true });
|
|
592
|
+
expect(rmMock).toHaveBeenCalledWith('/content/.output', { recursive: true, force: true });
|
|
593
|
+
});
|
|
594
|
+
it('runCleanCommand surfaces the missing-config error from loadMdsiteConfig', async () => {
|
|
595
|
+
loadConfigMock.mockRejectedValueOnce(new Error('Missing mdsite.yml in /content. Run `mdsite init` first.'));
|
|
596
|
+
await expect(runCleanCommand('/content')).rejects.toThrow('Missing mdsite.yml in /content. Run `mdsite init` first.');
|
|
597
|
+
expect(rmMock).not.toHaveBeenCalled();
|
|
598
|
+
});
|
|
517
599
|
});
|
|
518
600
|
//# sourceMappingURL=commands.test.js.map
|