jamdesk 1.1.39 → 1.1.41
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/dist/__tests__/unit/clean.test.d.ts +2 -0
- package/dist/__tests__/unit/clean.test.d.ts.map +1 -0
- package/dist/__tests__/unit/clean.test.js +59 -0
- package/dist/__tests__/unit/clean.test.js.map +1 -0
- package/dist/__tests__/unit/dev-cache-cleanup.test.d.ts +2 -0
- package/dist/__tests__/unit/dev-cache-cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/unit/dev-cache-cleanup.test.js +74 -0
- package/dist/__tests__/unit/dev-cache-cleanup.test.js.map +1 -0
- package/dist/__tests__/unit/dev-lock.test.d.ts +2 -0
- package/dist/__tests__/unit/dev-lock.test.d.ts.map +1 -0
- package/dist/__tests__/unit/dev-lock.test.js +70 -0
- package/dist/__tests__/unit/dev-lock.test.js.map +1 -0
- package/dist/__tests__/unit/dev-sync-vendored.test.d.ts +2 -0
- package/dist/__tests__/unit/dev-sync-vendored.test.d.ts.map +1 -0
- package/dist/__tests__/unit/dev-sync-vendored.test.js +58 -0
- package/dist/__tests__/unit/dev-sync-vendored.test.js.map +1 -0
- package/dist/__tests__/unit/run-build-script.test.d.ts +2 -0
- package/dist/__tests__/unit/run-build-script.test.d.ts.map +1 -0
- package/dist/__tests__/unit/run-build-script.test.js +58 -0
- package/dist/__tests__/unit/run-build-script.test.js.map +1 -0
- package/dist/__tests__/unit/workspace-paths.test.d.ts +2 -0
- package/dist/__tests__/unit/workspace-paths.test.d.ts.map +1 -0
- package/dist/__tests__/unit/workspace-paths.test.js +104 -0
- package/dist/__tests__/unit/workspace-paths.test.js.map +1 -0
- package/dist/commands/clean.d.ts +17 -2
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +47 -27
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/dev.d.ts +17 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +116 -95
- package/dist/commands/dev.js.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/deps.d.ts +0 -4
- package/dist/lib/deps.d.ts.map +1 -1
- package/dist/lib/deps.js +0 -6
- package/dist/lib/deps.js.map +1 -1
- package/dist/lib/dev-lock.d.ts +15 -0
- package/dist/lib/dev-lock.d.ts.map +1 -0
- package/dist/lib/dev-lock.js +95 -0
- package/dist/lib/dev-lock.js.map +1 -0
- package/dist/lib/run-build-script.d.ts +13 -0
- package/dist/lib/run-build-script.d.ts.map +1 -0
- package/dist/lib/run-build-script.js +36 -0
- package/dist/lib/run-build-script.js.map +1 -0
- package/dist/lib/safe-fs.d.ts +18 -0
- package/dist/lib/safe-fs.d.ts.map +1 -0
- package/dist/lib/safe-fs.js +55 -0
- package/dist/lib/safe-fs.js.map +1 -0
- package/dist/lib/workspace-paths.d.ts +37 -0
- package/dist/lib/workspace-paths.d.ts.map +1 -0
- package/dist/lib/workspace-paths.js +64 -0
- package/dist/lib/workspace-paths.js.map +1 -0
- package/package.json +1 -1
package/dist/commands/clean.js
CHANGED
|
@@ -1,49 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Clean Command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Default: clears the current project's workspace
|
|
5
|
+
* (`<jamdeskDir>/workspaces/<slug>/`).
|
|
6
|
+
*
|
|
7
|
+
* `--all`: clears everything under `<jamdeskDir>/`, including shared
|
|
8
|
+
* `node_modules` (next dev will reinstall on first run).
|
|
9
|
+
*
|
|
10
|
+
* Both modes use safeRemoveCache so a still-running dev session in the
|
|
11
|
+
* target tree surfaces a clear "another dev is running" message rather
|
|
12
|
+
* than crashing ENOTEMPTY.
|
|
5
13
|
*/
|
|
6
14
|
import fs from 'fs-extra';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
15
|
import { output } from '../lib/output.js';
|
|
10
16
|
import { spinner } from '../lib/spinner.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
import { getJamdeskDir } from '../lib/deps.js';
|
|
18
|
+
import { getProjectWorkspaceDir } from '../lib/workspace-paths.js';
|
|
19
|
+
import { safeRemoveCache } from '../lib/safe-fs.js';
|
|
20
|
+
export async function clean(options = {}, ctx = { jamdeskDir: getJamdeskDir(), projectDir: process.cwd() }) {
|
|
21
|
+
const { jamdeskDir, projectDir } = ctx;
|
|
22
|
+
if (options.all) {
|
|
23
|
+
if (!fs.existsSync(jamdeskDir)) {
|
|
24
|
+
output.info(`Nothing to clean — ${jamdeskDir} does not exist`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const spin = spinner('Calculating cache size...');
|
|
28
|
+
const size = await getDirectorySize(jamdeskDir);
|
|
29
|
+
spin.text = `Removing ${jamdeskDir} (${formatBytes(size)})...`;
|
|
30
|
+
await safeRemoveCache(jamdeskDir);
|
|
31
|
+
spin.succeed(`Cleared ${formatBytes(size)} of cache`);
|
|
32
|
+
console.log('\nCleared:');
|
|
33
|
+
console.log(` - ${jamdeskDir}/workspaces/ (all per-project caches)`);
|
|
34
|
+
console.log(` - ${jamdeskDir}/node_modules/ (installed dependencies)`);
|
|
35
|
+
console.log(` - ${jamdeskDir}/version-cache.json (update check cache)`);
|
|
36
|
+
console.log('\nNote: any currently-running `jamdesk dev` sessions will fail until you restart them.');
|
|
37
|
+
console.log('\nNext `jamdesk dev` will reinstall dependencies.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const workspaceDir = getProjectWorkspaceDir(jamdeskDir, projectDir);
|
|
41
|
+
if (!fs.existsSync(workspaceDir)) {
|
|
42
|
+
output.info(`Nothing to clean — no workspace at ${workspaceDir}`);
|
|
15
43
|
return;
|
|
16
44
|
}
|
|
17
|
-
const spin = spinner('Calculating
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log('\nCleared:');
|
|
25
|
-
console.log(' - ~/.jamdesk/workspace/ (Build cache)');
|
|
26
|
-
console.log(' - ~/.jamdesk/node_modules/ (installed dependencies)');
|
|
27
|
-
console.log(' - ~/.jamdesk/version-cache.json (update check cache)');
|
|
28
|
-
console.log('\nNext `jamdesk dev` will reinstall dependencies.');
|
|
45
|
+
const spin = spinner('Calculating workspace size...');
|
|
46
|
+
const size = await getDirectorySize(workspaceDir);
|
|
47
|
+
spin.text = `Removing ${workspaceDir} (${formatBytes(size)})...`;
|
|
48
|
+
await safeRemoveCache(workspaceDir);
|
|
49
|
+
spin.succeed(`Cleared ${formatBytes(size)} from this project's workspace`);
|
|
50
|
+
console.log(`\nNext \`jamdesk dev\` in this directory will recompile from scratch.`);
|
|
51
|
+
console.log(`Use \`jamdesk clean --all\` to also wipe shared node_modules.`);
|
|
29
52
|
}
|
|
30
53
|
async function getDirectorySize(dir) {
|
|
31
54
|
let size = 0;
|
|
32
55
|
try {
|
|
33
56
|
const files = await fs.readdir(dir, { withFileTypes: true });
|
|
34
57
|
for (const file of files) {
|
|
35
|
-
const filePath =
|
|
36
|
-
if (file.isDirectory())
|
|
58
|
+
const filePath = `${dir}/${file.name}`;
|
|
59
|
+
if (file.isDirectory())
|
|
37
60
|
size += await getDirectorySize(filePath);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const stats = await fs.stat(filePath);
|
|
41
|
-
size += stats.size;
|
|
42
|
-
}
|
|
61
|
+
else
|
|
62
|
+
size += (await fs.stat(filePath)).size;
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
catch {
|
|
46
|
-
//
|
|
66
|
+
// ignore (permissions, vanished entries)
|
|
47
67
|
}
|
|
48
68
|
return size;
|
|
49
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/dev.d.ts
CHANGED
|
@@ -27,5 +27,22 @@ export interface DevOptions {
|
|
|
27
27
|
webpack?: boolean;
|
|
28
28
|
clean?: boolean;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Sync vendored files to workspace, only copying files that changed.
|
|
32
|
+
* This preserves file timestamps for unchanged files, allowing Turbopack
|
|
33
|
+
* to reuse its cache and avoid 35s recompilation on every start.
|
|
34
|
+
*/
|
|
35
|
+
export declare function syncVendoredFiles(srcDir: string, destDir: string, filter: (relativePath: string) => boolean): Promise<{
|
|
36
|
+
copied: number;
|
|
37
|
+
skipped: number;
|
|
38
|
+
removed: number;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Validates the .next cache and cleans it if corrupted or stale.
|
|
42
|
+
* Returns true if cache is valid and usable, false if it was cleared.
|
|
43
|
+
*
|
|
44
|
+
* Exported for unit testing.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateAndCleanCache(nextCacheDir: string, cliVersionFile: string, workspaceDir: string, verbose: boolean): Promise<boolean>;
|
|
30
47
|
export declare function dev(options: DevOptions): Promise<void>;
|
|
31
48
|
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAO/E;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAiJD;;;;GAIG;AAGH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,GACxC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuE/D;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAqtB5D"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
* Wraps the dev-project.cjs logic from build-service.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync, spawn } from 'child_process';
|
|
8
|
+
import { runBuildScript } from '../lib/run-build-script.js';
|
|
8
9
|
import fs from 'fs-extra';
|
|
9
10
|
import path from 'path';
|
|
10
11
|
import { createRequire } from 'module';
|
|
11
12
|
import { output } from '../lib/output.js';
|
|
12
13
|
import { spinner } from '../lib/spinner.js';
|
|
13
|
-
import { ensureDependencies,
|
|
14
|
+
import { ensureDependencies, getJamdeskDir } from '../lib/deps.js';
|
|
15
|
+
import { getProjectWorkspaceDir, migrateLegacyWorkspace } from '../lib/workspace-paths.js';
|
|
16
|
+
import { acquireDevLock, releaseDevLock } from '../lib/dev-lock.js';
|
|
14
17
|
import { validateOpenApiSpec, formatErrorForCli, clearSpecCache } from '../lib/openapi.js';
|
|
15
18
|
import { validateConfig } from '../lib/docs-config.js';
|
|
16
19
|
import { validateBrandingAssets } from '../lib/validate-branding.js';
|
|
17
20
|
import { findAvailablePort } from '../lib/port.js';
|
|
18
21
|
import { createLoadingServer } from '../lib/dev-loading-server.js';
|
|
22
|
+
import { safeRemoveCache } from '../lib/safe-fs.js';
|
|
19
23
|
import { fileURLToPath } from 'url';
|
|
20
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
25
|
const __dirname = path.dirname(__filename);
|
|
@@ -151,7 +155,9 @@ async function syncDirectory(srcDir, destDir) {
|
|
|
151
155
|
* This preserves file timestamps for unchanged files, allowing Turbopack
|
|
152
156
|
* to reuse its cache and avoid 35s recompilation on every start.
|
|
153
157
|
*/
|
|
154
|
-
|
|
158
|
+
// Exported for unit testing — verifies the skip list preserves CLI metadata
|
|
159
|
+
// like the dev lockfile, which acquireDevLock writes before this runs.
|
|
160
|
+
export async function syncVendoredFiles(srcDir, destDir, filter) {
|
|
155
161
|
const stats = { copied: 0, skipped: 0, removed: 0 };
|
|
156
162
|
// Build set of source files (relative paths)
|
|
157
163
|
const sourceFiles = new Set();
|
|
@@ -188,9 +194,14 @@ async function syncVendoredFiles(srcDir, destDir, filter) {
|
|
|
188
194
|
// - node_modules: managed separately via NODE_PATH
|
|
189
195
|
// - projects: symlink to user's project
|
|
190
196
|
// - public: contains project-specific files added after sync (docs.json, images)
|
|
197
|
+
// Plus any CLI-managed dotfile written before sync (e.g. the dev lockfile
|
|
198
|
+
// acquireDevLock writes at workspace root) — don't sweep our own metadata.
|
|
191
199
|
if (entry.name === '.next' || entry.name === 'node_modules' || entry.name === 'projects' || entry.name === 'public') {
|
|
192
200
|
continue;
|
|
193
201
|
}
|
|
202
|
+
if (entry.name === '.jamdesk-dev.lock' || entry.name === '.content-hash') {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
194
205
|
const destPath = path.join(dir, entry.name);
|
|
195
206
|
if (entry.isDirectory()) {
|
|
196
207
|
await walkDest(destPath, relativePath);
|
|
@@ -212,8 +223,10 @@ async function syncVendoredFiles(srcDir, destDir, filter) {
|
|
|
212
223
|
/**
|
|
213
224
|
* Validates the .next cache and cleans it if corrupted or stale.
|
|
214
225
|
* Returns true if cache is valid and usable, false if it was cleared.
|
|
226
|
+
*
|
|
227
|
+
* Exported for unit testing.
|
|
215
228
|
*/
|
|
216
|
-
async function validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir, verbose) {
|
|
229
|
+
export async function validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir, verbose) {
|
|
217
230
|
const cachedVersion = fs.existsSync(cliVersionFile)
|
|
218
231
|
? fs.readFileSync(cliVersionFile, 'utf-8').trim()
|
|
219
232
|
: null;
|
|
@@ -222,7 +235,7 @@ async function validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir,
|
|
|
222
235
|
if (cachedVersion !== currentVersion) {
|
|
223
236
|
if (verbose)
|
|
224
237
|
output.info(`CLI version changed (${cachedVersion} → ${currentVersion}), clearing cache`);
|
|
225
|
-
await
|
|
238
|
+
await safeRemoveCache(nextCacheDir);
|
|
226
239
|
return false;
|
|
227
240
|
}
|
|
228
241
|
// Check for Turbopack cache corruption (workspace deleted while dev server running)
|
|
@@ -230,7 +243,7 @@ async function validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir,
|
|
|
230
243
|
const appDir = path.join(workspaceDir, 'app');
|
|
231
244
|
if (fs.existsSync(turbopackCache) && !fs.existsSync(appDir)) {
|
|
232
245
|
output.warn('Detected corrupted cache, clearing...');
|
|
233
|
-
await
|
|
246
|
+
await safeRemoveCache(nextCacheDir);
|
|
234
247
|
return false;
|
|
235
248
|
}
|
|
236
249
|
// Remove stale lock file from previous runs (prevents "another dev server" warning)
|
|
@@ -351,7 +364,38 @@ export async function dev(options) {
|
|
|
351
364
|
// Step 2: Prepare workspace
|
|
352
365
|
spin = spinner('Preparing workspace...');
|
|
353
366
|
const jamdeskDir = getJamdeskDir();
|
|
354
|
-
|
|
367
|
+
// Drop the legacy single-workspace layout (CLI ≤1.1.40). safeRemoveCache
|
|
368
|
+
// surfaces a friendly message if a 1.1.40 dev session is still holding it.
|
|
369
|
+
if (await migrateLegacyWorkspace(jamdeskDir)) {
|
|
370
|
+
if (verbose)
|
|
371
|
+
output.info('Migrated legacy ~/.jamdesk/workspace/ to per-project layout');
|
|
372
|
+
}
|
|
373
|
+
const workspaceDir = getProjectWorkspaceDir(jamdeskDir, projectDir);
|
|
374
|
+
// Same-project guard: refuse to start if another dev is using this workspace.
|
|
375
|
+
// Acquired BEFORE --clean so a concurrent `dev --clean` can't wipe our
|
|
376
|
+
// lockfile out from under us.
|
|
377
|
+
const lock = acquireDevLock(workspaceDir);
|
|
378
|
+
if (!lock.acquired) {
|
|
379
|
+
spin.fail('Another `jamdesk dev` is already running for this project');
|
|
380
|
+
output.error(` PID ${lock.heldBy} holds the lock at ${path.join(workspaceDir, '.jamdesk-dev.lock')}.\n` +
|
|
381
|
+
` Stop it first (Ctrl-C in that terminal, or kill ${lock.heldBy}), then retry.`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
// Release the lock on any normal exit path. SIGINT/SIGTERM additionally
|
|
385
|
+
// run `cleanup` further below, which calls release() before tree-killing
|
|
386
|
+
// the spawned next dev process group.
|
|
387
|
+
const release = () => releaseDevLock(workspaceDir);
|
|
388
|
+
process.once('exit', release);
|
|
389
|
+
// Handle --clean: wipe workspace contents but preserve the lockfile we just
|
|
390
|
+
// acquired. safeRemoveCache surfaces the friendly message if any path is
|
|
391
|
+
// still held open.
|
|
392
|
+
if (options.clean) {
|
|
393
|
+
output.info('Clearing workspace cache...');
|
|
394
|
+
const entries = await fs.readdir(workspaceDir);
|
|
395
|
+
await Promise.all(entries
|
|
396
|
+
.filter((name) => name !== '.jamdesk-dev.lock')
|
|
397
|
+
.map((name) => safeRemoveCache(path.join(workspaceDir, name))));
|
|
398
|
+
}
|
|
355
399
|
const vendoredDir = path.join(__dirname, '../../vendored');
|
|
356
400
|
// Check if vendored directory exists
|
|
357
401
|
if (!fs.existsSync(vendoredDir)) {
|
|
@@ -363,58 +407,46 @@ export async function dev(options) {
|
|
|
363
407
|
// Only remove and recopy vendored files, keeping the .next directory
|
|
364
408
|
const nextCacheDir = path.join(workspaceDir, '.next');
|
|
365
409
|
const cliVersionFile = path.join(nextCacheDir, '.jamdesk-cli-version');
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
output.info('Clearing workspace cache...');
|
|
369
|
-
await fs.remove(workspaceDir);
|
|
370
|
-
}
|
|
371
|
-
// Determine if we have usable cached compilation
|
|
372
|
-
let hadCache = fs.existsSync(nextCacheDir);
|
|
373
|
-
if (hadCache) {
|
|
374
|
-
hadCache = await validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir, verbose);
|
|
375
|
-
}
|
|
410
|
+
const hadCache = fs.existsSync(nextCacheDir)
|
|
411
|
+
&& await validateAndCleanCache(nextCacheDir, cliVersionFile, workspaceDir, verbose);
|
|
376
412
|
// Ensure workspace directory exists
|
|
377
413
|
await fs.ensureDir(workspaceDir);
|
|
378
414
|
// Sync vendored files - only copy files that actually changed
|
|
379
415
|
// This preserves timestamps for unchanged files, allowing Turbopack to reuse cache
|
|
380
416
|
const syncStats = await syncVendoredFiles(vendoredDir, workspaceDir, (relativePath) => !relativePath.includes('node_modules') && !relativePath.includes('.next'));
|
|
381
|
-
// Log sync stats in verbose mode only (internal detail)
|
|
382
417
|
if (verbose && syncStats.copied > 0) {
|
|
383
418
|
output.info(`Synced ${syncStats.copied} build files (${syncStats.skipped} unchanged)`);
|
|
384
419
|
}
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// NODE_PATH tells Node.js where to find modules without needing a symlink
|
|
388
|
-
const jamdeskNodeModules = path.join(path.dirname(workspaceDir), 'node_modules');
|
|
389
|
-
// Clean up any stale node_modules symlink (Turbopack doesn't allow external symlinks)
|
|
420
|
+
// Turbopack rejects external symlinks (`Symlink node_modules is invalid`),
|
|
421
|
+
// so use NODE_PATH=depsDir below instead and clear any stale symlink here.
|
|
390
422
|
const workspaceNodeModules = path.join(workspaceDir, 'node_modules');
|
|
391
|
-
|
|
423
|
+
try {
|
|
392
424
|
const stats = await fs.lstat(workspaceNodeModules);
|
|
393
|
-
if (stats.isSymbolicLink())
|
|
425
|
+
if (stats.isSymbolicLink())
|
|
394
426
|
await fs.remove(workspaceNodeModules);
|
|
395
|
-
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
if (err.code !== 'ENOENT')
|
|
430
|
+
throw err;
|
|
396
431
|
}
|
|
397
432
|
// Symlink public directory to parent so Turbopack can find static files
|
|
398
433
|
// (outputFileTracingRoot and turbopack.root are set to parent directory)
|
|
399
434
|
const parentPublic = path.join(jamdeskDir, 'public');
|
|
400
435
|
const workspacePublic = path.join(workspaceDir, 'public');
|
|
401
436
|
await fs.ensureDir(workspacePublic);
|
|
402
|
-
// Only recreate symlink if target changed
|
|
437
|
+
// Only recreate symlink if target changed (preserves Turbopack cache mtimes)
|
|
403
438
|
let needsPublicSymlink = true;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const currentTarget = await fs.readlink(parentPublic);
|
|
409
|
-
if (currentTarget === workspacePublic) {
|
|
410
|
-
needsPublicSymlink = false;
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
await fs.remove(parentPublic);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
439
|
+
try {
|
|
440
|
+
const stats = await fs.lstat(parentPublic);
|
|
441
|
+
if (stats.isSymbolicLink() && (await fs.readlink(parentPublic)) === workspacePublic) {
|
|
442
|
+
needsPublicSymlink = false;
|
|
416
443
|
}
|
|
417
|
-
|
|
444
|
+
else {
|
|
445
|
+
await fs.remove(parentPublic);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
if (err.code !== 'ENOENT') {
|
|
418
450
|
await fs.remove(parentPublic);
|
|
419
451
|
}
|
|
420
452
|
}
|
|
@@ -427,28 +459,23 @@ export async function dev(options) {
|
|
|
427
459
|
const workspaceProjectsDir = path.join(workspaceDir, 'projects');
|
|
428
460
|
const workspaceProjectDir = path.join(workspaceProjectsDir, projectName);
|
|
429
461
|
await fs.ensureDir(workspaceProjectsDir);
|
|
430
|
-
// Only recreate symlink if target changed (preserves timestamps for Turbopack cache)
|
|
431
462
|
let needsSymlink = true;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (currentTarget === projectDir) {
|
|
436
|
-
needsSymlink = false; // Symlink already points to correct location
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
await fs.remove(workspaceProjectDir);
|
|
440
|
-
}
|
|
463
|
+
try {
|
|
464
|
+
if ((await fs.readlink(workspaceProjectDir)) === projectDir) {
|
|
465
|
+
needsSymlink = false;
|
|
441
466
|
}
|
|
442
|
-
|
|
443
|
-
|
|
467
|
+
else {
|
|
468
|
+
await fs.remove(workspaceProjectDir);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
if (err.code !== 'ENOENT') {
|
|
444
473
|
await fs.remove(workspaceProjectDir);
|
|
445
474
|
}
|
|
446
475
|
}
|
|
447
476
|
if (needsSymlink) {
|
|
448
477
|
await fs.symlink(projectDir, workspaceProjectDir, 'junction');
|
|
449
478
|
}
|
|
450
|
-
// Copy project's docs.json to workspace/public/ (only if changed)
|
|
451
|
-
await fs.ensureDir(path.join(workspaceDir, 'public'));
|
|
452
479
|
await copyIfChanged(docsJsonPath, path.join(workspaceDir, 'public/docs.json'));
|
|
453
480
|
spin.succeed('Workspace prepared');
|
|
454
481
|
// Step 3: Copy project assets (images, etc.) - only copy changed files
|
|
@@ -500,30 +527,31 @@ export async function dev(options) {
|
|
|
500
527
|
// Compute content hash to detect changes (based on file mtimes for speed)
|
|
501
528
|
const crypto = await import('crypto');
|
|
502
529
|
const { glob } = await import('glob');
|
|
530
|
+
const statMtime = async (file, root) => {
|
|
531
|
+
try {
|
|
532
|
+
const stat = await fs.stat(path.join(root, file));
|
|
533
|
+
return { key: file, mtimeMs: stat.mtimeMs };
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
503
539
|
const computeContentHash = async () => {
|
|
504
540
|
const hash = crypto.createHash('md5');
|
|
505
|
-
// Hash docs.json content
|
|
506
541
|
hash.update(await fs.readFile(docsJsonPath, 'utf-8'));
|
|
507
|
-
// Glob only MDX/MD files
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const stat = await fs.stat(path.join(projectDir, file));
|
|
514
|
-
hash.update(`${file}:${stat.mtimeMs}`);
|
|
515
|
-
}
|
|
516
|
-
catch { /* skip inaccessible files */ }
|
|
542
|
+
// Glob only MDX/MD files — recursive readdir walks node_modules/.git.
|
|
543
|
+
const mdxFiles = (await glob('**/*.{mdx,md}', { cwd: projectDir, nodir: true })).sort();
|
|
544
|
+
const mdxStats = await Promise.all(mdxFiles.map((f) => statMtime(f, projectDir)));
|
|
545
|
+
for (const s of mdxStats) {
|
|
546
|
+
if (s)
|
|
547
|
+
hash.update(`${s.key}:${s.mtimeMs}`);
|
|
517
548
|
}
|
|
518
|
-
// Hash snippets mtimes if they exist
|
|
519
549
|
if (hasSnippets) {
|
|
520
|
-
const snippetFiles = await fs.readdir(snippetsDir);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
hash.update(`snippets/${
|
|
525
|
-
}
|
|
526
|
-
catch { /* skip */ }
|
|
550
|
+
const snippetFiles = (await fs.readdir(snippetsDir)).sort();
|
|
551
|
+
const snippetStats = await Promise.all(snippetFiles.map((f) => statMtime(f, snippetsDir)));
|
|
552
|
+
for (const s of snippetStats) {
|
|
553
|
+
if (s)
|
|
554
|
+
hash.update(`snippets/${s.key}:${s.mtimeMs}`);
|
|
527
555
|
}
|
|
528
556
|
}
|
|
529
557
|
return hash.digest('hex');
|
|
@@ -544,34 +572,26 @@ export async function dev(options) {
|
|
|
544
572
|
const contentChanged = currentHash !== cachedHash;
|
|
545
573
|
if (contentChanged) {
|
|
546
574
|
spin.text = 'Processing navigation, snippets, and search index...';
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
execSync(`node scripts/${script}`, {
|
|
552
|
-
cwd: workspaceDir,
|
|
553
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
554
|
-
env: { ...env, PROJECT_NAME: path.basename(projectDir) },
|
|
555
|
-
});
|
|
556
|
-
resolve({ script, success: true });
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
resolve({ script, success: false, error });
|
|
560
|
-
}
|
|
561
|
-
});
|
|
575
|
+
const scriptOpts = {
|
|
576
|
+
cwd: workspaceDir,
|
|
577
|
+
env: { ...env, PROJECT_NAME: path.basename(projectDir) },
|
|
578
|
+
verbose,
|
|
562
579
|
};
|
|
563
|
-
// Run all three scripts in parallel
|
|
564
580
|
const [navResult, snippetsResult, searchResult] = await Promise.all([
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
581
|
+
runBuildScript('enhance-navigation.cjs', scriptOpts),
|
|
582
|
+
runBuildScript('compile-snippets.cjs', scriptOpts),
|
|
583
|
+
runBuildScript('build-search-index.cjs', scriptOpts),
|
|
568
584
|
]);
|
|
569
|
-
// Check for failures
|
|
570
585
|
const failures = [navResult, snippetsResult, searchResult].filter((r) => !r.success);
|
|
571
586
|
if (failures.length > 0) {
|
|
572
587
|
spin.fail('Build step failed');
|
|
573
588
|
for (const f of failures) {
|
|
574
589
|
output.error(`Failed: ${f.script}`);
|
|
590
|
+
// Surface child stderr (or stdout) on every failure so non-verbose users
|
|
591
|
+
// see the actual error, not just the script name.
|
|
592
|
+
const tail = (f.stderr || f.stdout || '').trim();
|
|
593
|
+
if (tail)
|
|
594
|
+
console.error(tail.split('\n').slice(-20).join('\n'));
|
|
575
595
|
if (verbose && f.error)
|
|
576
596
|
console.error(f.error);
|
|
577
597
|
}
|
|
@@ -634,7 +654,7 @@ export async function dev(options) {
|
|
|
634
654
|
console.log(' If it gets stuck at "Starting...", run `jamdesk clean` and restart.\n');
|
|
635
655
|
// Show cold start warning if no cache
|
|
636
656
|
if (!hadCache) {
|
|
637
|
-
console.log(`\n ⏳ First run - full compile needed (this takes
|
|
657
|
+
console.log(`\n ⏳ First run - full compile needed (this takes a minute, then it's cached)`);
|
|
638
658
|
}
|
|
639
659
|
console.log(`\n Access at: http://localhost:${proxyPort}${basePath}/${firstPage}\n`);
|
|
640
660
|
// Use Turbopack by default (~5x faster than webpack)
|
|
@@ -857,6 +877,7 @@ export async function dev(options) {
|
|
|
857
877
|
});
|
|
858
878
|
// Handle Ctrl+C - be aggressive about cleanup
|
|
859
879
|
function cleanup() {
|
|
880
|
+
release(); // Release the workspace lock first so a subsequent dev isn't blocked.
|
|
860
881
|
if (compileSpinner) {
|
|
861
882
|
clearInterval(compileSpinner);
|
|
862
883
|
process.stdout.write('\r\x1b[K');
|
|
@@ -882,7 +903,7 @@ export async function dev(options) {
|
|
|
882
903
|
}
|
|
883
904
|
process.exit(0);
|
|
884
905
|
}
|
|
885
|
-
process.
|
|
886
|
-
process.
|
|
906
|
+
process.once('SIGINT', cleanup);
|
|
907
|
+
process.once('SIGTERM', cleanup);
|
|
887
908
|
}
|
|
888
909
|
//# sourceMappingURL=dev.js.map
|