Skip to content

Commit 4e45c71

Browse files
tmp dir handling
1 parent f4218ad commit 4e45c71

File tree

15 files changed

+138
-210
lines changed

15 files changed

+138
-210
lines changed

packages/cli/src/__tests__/bundle/bundler.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ function createBuildOptions(
4545
minify: false,
4646
sourcemap: false,
4747
cache: true,
48-
tempDir: '.tmp',
4948
packages: {},
5049
code: '',
5150
...overrides,

packages/cli/src/__tests__/core/config.test.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import fs from 'fs-extra';
22
import path from 'path';
3-
import {
4-
loadJsonConfig,
5-
substituteEnvVariables,
6-
getTempDir,
7-
} from '../../config/index.js';
3+
import { loadJsonConfig, substituteEnvVariables } from '../../config/index.js';
4+
import { getTmpPath, getDefaultTmpRoot } from '../../core/tmp.js';
85
import { getId } from '@walkeros/core';
96

107
describe('Config utilities', () => {
@@ -86,21 +83,30 @@ describe('Config utilities', () => {
8683
});
8784
});
8885

89-
describe('getTempDir', () => {
90-
it('should generate unique temp directory', () => {
91-
const dir1 = getTempDir();
92-
const dir2 = getTempDir();
86+
describe('getTmpPath', () => {
87+
it('should return absolute tmp root when no arguments', () => {
88+
const tmpPath = getTmpPath();
9389

94-
expect(dir1).not.toBe(dir2);
95-
expect(dir1).toContain('cli-');
96-
expect(dir2).toContain('cli-');
90+
expect(tmpPath).toBe(path.resolve('.tmp'));
91+
expect(path.isAbsolute(tmpPath)).toBe(true);
9792
});
9893

99-
it('should use custom temp directory name', () => {
100-
const customDir = getTempDir('custom-temp');
94+
it('should join path segments', () => {
95+
const tmpPath = getTmpPath(undefined, 'cache', 'builds');
10196

102-
expect(customDir).toContain('custom-temp');
103-
expect(customDir).toContain('cli-');
97+
expect(tmpPath).toBe(path.resolve('.tmp', 'cache', 'builds'));
98+
expect(path.isAbsolute(tmpPath)).toBe(true);
99+
});
100+
101+
it('should use custom tmp directory', () => {
102+
const tmpPath = getTmpPath('/custom', 'cache');
103+
104+
expect(tmpPath).toBe(path.join('/custom', 'cache'));
105+
expect(path.isAbsolute(tmpPath)).toBe(true);
106+
});
107+
108+
it('should return default tmp root from getDefaultTmpRoot', () => {
109+
expect(getDefaultTmpRoot()).toBe('.tmp');
104110
});
105111
});
106112
});

packages/cli/src/commands/bundle/bundler.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { packageNameToVariable } from '@walkeros/core';
66
import type { BuildOptions } from '../../types/bundle.js';
77
import { downloadPackages } from './package-manager.js';
88
import type { Logger } from '../../core/index.js';
9-
import { getTempDir } from '../../config/index.js';
9+
import { getTmpPath } from '../../core/tmp.js';
1010
import {
1111
isBuildCached,
1212
getCachedBuild,
@@ -73,14 +73,8 @@ export async function bundleCore(
7373
showStats = false,
7474
): Promise<BundleStats | void> {
7575
const bundleStartTime = Date.now();
76-
// Only generate a new temp dir if one isn't explicitly provided
77-
// This allows simulator to share its temp dir with the bundler
78-
// Ensure TEMP_DIR is always absolute (esbuild requirement)
79-
const TEMP_DIR = buildOptions.tempDir
80-
? path.isAbsolute(buildOptions.tempDir)
81-
? buildOptions.tempDir
82-
: path.resolve(buildOptions.tempDir)
83-
: getTempDir();
76+
// Use provided temp dir or default .tmp/
77+
const TEMP_DIR = buildOptions.tempDir || getTmpPath();
8478

8579
// Check build cache if caching is enabled
8680
if (buildOptions.cache !== false) {
@@ -123,12 +117,8 @@ export async function bundleCore(
123117
}
124118

125119
try {
126-
// Step 1: Prepare temporary directory
127-
// Only clean if we created a new temp dir (don't clean shared simulator temp)
128-
if (!buildOptions.tempDir) {
129-
await fs.emptyDir(TEMP_DIR);
130-
}
131-
logger.debug('Cleaned temporary directory');
120+
// Step 1: Ensure temporary directory exists
121+
await fs.ensureDir(TEMP_DIR);
132122

133123
// Step 2: Download packages
134124
logger.info('📥 Downloading packages...');
@@ -245,19 +235,10 @@ export async function bundleCore(
245235
);
246236
}
247237

248-
// Step 8: Cleanup
249-
// Only cleanup if we created our own temp dir (not shared with simulator)
250-
if (!buildOptions.tempDir) {
251-
await fs.remove(TEMP_DIR);
252-
logger.debug('Cleaned up temporary files');
253-
}
238+
// No auto-cleanup - user runs `walkeros clean` explicitly
254239

255240
return stats;
256241
} catch (error) {
257-
// Cleanup on error (only if we created our own temp dir)
258-
if (!buildOptions.tempDir) {
259-
await fs.remove(TEMP_DIR).catch(() => {});
260-
}
261242
throw error;
262243
}
263244
}

packages/cli/src/commands/bundle/package-manager.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
copyLocalPackage,
88
} from '../../core/index.js';
99
import { getPackageCacheKey } from '../../core/cache-utils.js';
10+
import { getTmpPath } from '../../core/tmp.js';
1011

1112
/** Timeout for individual package downloads (60 seconds) */
1213
const PACKAGE_DOWNLOAD_TIMEOUT_MS = 60000;
@@ -55,18 +56,18 @@ function getPackageDirectory(
5556

5657
async function getCachedPackagePath(
5758
pkg: Package,
58-
tempDir: string,
59+
tmpDir?: string,
5960
): Promise<string> {
60-
const cacheDir = path.join('.tmp', 'cache', 'packages');
61+
const cacheDir = getTmpPath(tmpDir, 'cache', 'packages');
6162
const cacheKey = await getPackageCacheKey(pkg.name, pkg.version);
6263
return path.join(cacheDir, cacheKey);
6364
}
6465

6566
async function isPackageCached(
6667
pkg: Package,
67-
tempDir: string,
68+
tmpDir?: string,
6869
): Promise<boolean> {
69-
const cachedPath = await getCachedPackagePath(pkg, tempDir);
70+
const cachedPath = await getCachedPackagePath(pkg, tmpDir);
7071
return fs.pathExists(cachedPath);
7172
}
7273

@@ -204,9 +205,10 @@ export async function downloadPackages(
204205
const packageSpec = `${pkg.name}@${pkg.version}`;
205206
// Use proper node_modules structure: node_modules/@scope/package
206207
const packageDir = getPackageDirectory(targetDir, pkg.name, pkg.version);
207-
const cachedPath = await getCachedPackagePath(pkg, targetDir);
208+
// Cache always uses the default .tmp/cache/packages location
209+
const cachedPath = await getCachedPackagePath(pkg);
208210

209-
if (useCache && (await isPackageCached(pkg, targetDir))) {
211+
if (useCache && (await isPackageCached(pkg))) {
210212
logger.debug(`Using cached ${packageSpec}...`);
211213
try {
212214
// Ensure parent directories exist for scoped packages (@scope/package)

packages/cli/src/commands/cache.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import fs from 'fs-extra';
2-
import path from 'path';
32
import { Command } from 'commander';
4-
5-
const CACHE_DIR = path.join('.tmp', 'cache');
3+
import { getTmpPath, getDefaultTmpRoot } from '../core/tmp.js';
64

75
export function registerCacheCommand(program: Command): void {
86
const cache = program.command('cache').description('Manage the CLI cache');
@@ -12,35 +10,54 @@ export function registerCacheCommand(program: Command): void {
1210
.description('Clear all cached packages and builds')
1311
.option('--packages', 'Clear only package cache')
1412
.option('--builds', 'Clear only build cache')
13+
.option('--tmp-dir <dir>', 'Custom temp directory')
1514
.action(async (options) => {
15+
const tmpDir = options.tmpDir;
1616
if (options.packages) {
17-
await fs.remove(path.join(CACHE_DIR, 'packages'));
17+
await fs.remove(getTmpPath(tmpDir, 'cache', 'packages'));
1818
console.log('Package cache cleared');
1919
} else if (options.builds) {
20-
await fs.remove(path.join(CACHE_DIR, 'builds'));
20+
await fs.remove(getTmpPath(tmpDir, 'cache', 'builds'));
2121
console.log('Build cache cleared');
2222
} else {
23-
await fs.remove(CACHE_DIR);
23+
await fs.remove(getTmpPath(tmpDir, 'cache'));
2424
console.log('All caches cleared');
2525
}
2626
});
2727

2828
cache
2929
.command('info')
3030
.description('Show cache statistics')
31-
.action(async () => {
32-
const packagesDir = path.join(CACHE_DIR, 'packages');
33-
const buildsDir = path.join(CACHE_DIR, 'builds');
31+
.option('--tmp-dir <dir>', 'Custom temp directory')
32+
.action(async (options) => {
33+
const tmpDir = options.tmpDir;
34+
const packagesDir = getTmpPath(tmpDir, 'cache', 'packages');
35+
const buildsDir = getTmpPath(tmpDir, 'cache', 'builds');
3436

3537
const packageCount = await countEntries(packagesDir);
3638
const buildCount = await countEntries(buildsDir);
3739

38-
console.log(`Cache directory: ${CACHE_DIR}`);
40+
console.log(`Cache directory: ${getTmpPath(tmpDir, 'cache')}`);
3941
console.log(`Cached packages: ${packageCount}`);
4042
console.log(`Cached builds: ${buildCount}`);
4143
});
4244
}
4345

46+
/**
47+
* Register the clean command to clear entire temp directory
48+
*/
49+
export function registerCleanCommand(program: Command): void {
50+
program
51+
.command('clean')
52+
.description('Clear the entire temp directory (.tmp/)')
53+
.option('--tmp-dir <dir>', 'Custom temp directory')
54+
.action(async (options) => {
55+
const tmpDir = options.tmpDir || getDefaultTmpRoot();
56+
await fs.remove(tmpDir);
57+
console.log(`Temp directory cleared: ${tmpDir}`);
58+
});
59+
}
60+
4461
async function countEntries(dir: string): Promise<number> {
4562
if (!(await fs.pathExists(dir))) return 0;
4663
const entries = await fs.readdir(dir);

packages/cli/src/commands/simulate/simulator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { createLogger, getErrorMessage } from '../../core/index.js';
66
import {
77
loadJsonConfig,
88
loadBundleConfig,
9-
getTempDir,
109
isObject,
1110
type BuildOptions,
1211
} from '../../config/index.js';
12+
import { getTmpPath } from '../../core/tmp.js';
1313
import { bundleCore } from '../bundle/bundler.js';
1414
import { CallTracker } from './tracker.js';
1515
import { executeInJSDOM } from './jsdom-executor.js';
@@ -102,7 +102,7 @@ export async function executeSimulation(
102102
): Promise<SimulationResult> {
103103
const startTime = Date.now();
104104
let bundlePath: string | undefined;
105-
const tempDir = getTempDir();
105+
const tempDir = getTmpPath();
106106

107107
try {
108108
// Validate event format

packages/cli/src/config/build-defaults.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const WEB_BUILD_DEFAULTS: Omit<BuildOptions, 'output' | 'packages'> = {
2020
minify: true,
2121
sourcemap: false,
2222
cache: true,
23-
tempDir: '.tmp',
2423
windowCollector: 'collector',
2524
windowElb: 'elb',
2625
};
@@ -39,7 +38,6 @@ export const SERVER_BUILD_DEFAULTS: Omit<BuildOptions, 'output' | 'packages'> =
3938
minify: true,
4039
sourcemap: false,
4140
cache: true,
42-
tempDir: '.tmp',
4341
};
4442

4543
/**

packages/cli/src/config/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export {
2828
substituteEnvVariables,
2929
loadJsonConfig,
3030
loadJsonFromSource,
31-
getTempDir,
31+
isUrl,
3232
} from './utils.js';
3333

3434
// Loader

packages/cli/src/config/loader.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getAvailableFlows as getFlowNames,
1616
} from './validators.js';
1717
import { getBuildDefaults, getDefaultOutput } from './build-defaults.js';
18+
import { isUrl } from './utils.js';
1819

1920
/** Default folder for includes if it exists */
2021
const DEFAULT_INCLUDE_FOLDER = './shared';
@@ -102,19 +103,15 @@ export function loadBundleConfig(
102103
// Extract packages from flowConfig (if present)
103104
const packages = flowConfig.packages || {};
104105

105-
// Resolve output path relative to config directory
106-
let output = getDefaultOutput(platform);
107-
if (options.buildOverrides?.output) {
108-
output = options.buildOverrides.output;
109-
}
110-
111-
// Get config directory for relative path resolution
112-
const configDir = path.dirname(options.configPath);
106+
// Output path: use --output if provided, otherwise default
107+
// Always relative to cwd, no dynamic resolution
108+
const output = options.buildOverrides?.output || getDefaultOutput(platform);
113109

114-
// Make output path absolute relative to config file
115-
if (!path.isAbsolute(output)) {
116-
output = path.resolve(configDir, output);
117-
}
110+
// Get config directory for resolving includes and local packages
111+
// For URLs, use cwd since there's no local config directory
112+
const configDir = isUrl(options.configPath)
113+
? process.cwd()
114+
: path.dirname(options.configPath);
118115

119116
// Get includes from config or use default if ./shared exists
120117
let includes = setup.include;

packages/cli/src/config/utils.ts

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import fs from 'fs-extra';
66
import path from 'path';
7-
import os from 'os';
87
import { getErrorMessage } from '../core/index.js';
8+
import { getTmpPath } from '../core/tmp.js';
99

1010
/**
1111
* Check if a string is a valid URL
@@ -51,15 +51,12 @@ export async function downloadFromUrl(url: string): Promise<string> {
5151

5252
const content = await response.text();
5353

54-
// Extract filename from URL or generate one
55-
const urlObj = new URL(url);
56-
const urlFilename = path.basename(urlObj.pathname);
57-
const extension = path.extname(urlFilename) || '.json';
58-
const randomId = Math.random().toString(36).substring(2, 11);
59-
const filename = `walkeros-download-${Date.now()}-${randomId}${extension}`;
54+
// Write to .tmp/downloads/ directory
55+
const downloadsDir = getTmpPath(undefined, 'downloads');
56+
await fs.ensureDir(downloadsDir);
6057

61-
// Write to system temp directory
62-
const tempPath = path.join(os.tmpdir(), filename);
58+
// Use a consistent filename - always re-downloaded fresh anyway
59+
const tempPath = path.join(downloadsDir, 'flow.json');
6360
await fs.writeFile(tempPath, content, 'utf-8');
6461

6562
return tempPath;
@@ -146,26 +143,6 @@ export async function loadJsonConfig<T>(configPath: string): Promise<T> {
146143
}
147144
}
148145

149-
/**
150-
* Generate a unique temporary directory path.
151-
*
152-
* @param tempDir - Base temporary directory (default: ".tmp")
153-
* @returns Absolute path to unique temp directory
154-
*
155-
* @example
156-
* ```typescript
157-
* getTempDir() // "/workspaces/project/.tmp/cli-1647261462000-abc123"
158-
* getTempDir('/tmp') // "/tmp/cli-1647261462000-abc123"
159-
* ```
160-
*/
161-
export function getTempDir(tempDir = '.tmp'): string {
162-
const randomId = Math.random().toString(36).substring(2, 11);
163-
const basePath = path.isAbsolute(tempDir)
164-
? tempDir
165-
: path.join(process.cwd(), tempDir);
166-
return path.join(basePath, `cli-${Date.now()}-${randomId}`);
167-
}
168-
169146
/**
170147
* Load JSON from inline string, file path, or URL.
171148
*

0 commit comments

Comments
 (0)