seabox 0.1.1 → 0.2.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.
@@ -1,199 +1,199 @@
1
- /**
2
- * build-cache.mjs
3
- * Build caching system to speed up incremental builds.
4
- */
5
-
6
- import fs from 'fs';
7
- import path from 'path';
8
- import crypto from 'crypto';
9
-
10
- /**
11
- * @typedef {Object} CacheEntry
12
- * @property {string} hash - Content hash of input
13
- * @property {number} timestamp - When cached
14
- * @property {any} data - Cached data
15
- */
16
-
17
- export class BuildCache {
18
- /**
19
- * @param {string} cacheDir - Cache directory path
20
- */
21
- constructor(cacheDir = '.seabox-cache') {
22
- this.cacheDir = cacheDir;
23
- this.ensureCacheDir();
24
- }
25
-
26
- /**
27
- * Ensure cache directory exists
28
- */
29
- ensureCacheDir() {
30
- if (!fs.existsSync(this.cacheDir)) {
31
- fs.mkdirSync(this.cacheDir, { recursive: true });
32
- }
33
-
34
- // Create subdirectories
35
- const subdirs = ['bundles', 'natives', 'blobs'];
36
- for (const subdir of subdirs) {
37
- const dirPath = path.join(this.cacheDir, subdir);
38
- if (!fs.existsSync(dirPath)) {
39
- fs.mkdirSync(dirPath, { recursive: true });
40
- }
41
- }
42
- }
43
-
44
- /**
45
- * Compute cache key from entry path and config
46
- * @param {string} entryPath - Entry file path
47
- * @param {Object} config - Build config
48
- * @returns {string}
49
- */
50
- computeCacheKey(entryPath, config) {
51
- const configStr = JSON.stringify({
52
- bundler: config.bundler,
53
- entry: entryPath
54
- });
55
-
56
- return crypto
57
- .createHash('sha256')
58
- .update(configStr)
59
- .digest('hex')
60
- .substring(0, 16);
61
- }
62
-
63
- /**
64
- * Get cached bundle if valid
65
- * @param {string} entryPath - Entry file path
66
- * @param {Object} config - Build config
67
- * @returns {Object|null}
68
- */
69
- async getCachedBundle(entryPath, config) {
70
- const cacheKey = this.computeCacheKey(entryPath, config);
71
- const cachePath = path.join(this.cacheDir, 'bundles', `${cacheKey}.json`);
72
-
73
- if (!fs.existsSync(cachePath)) {
74
- return null;
75
- }
76
-
77
- try {
78
- const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
79
-
80
- // Validate that source files haven't changed
81
- if (await this.isValid(cached, entryPath)) {
82
- return cached;
83
- }
84
- } catch (err) {
85
- // Invalid cache entry
86
- }
87
-
88
- return null;
89
- }
90
-
91
- /**
92
- * Cache a bundle result
93
- * @param {string} entryPath - Entry file path
94
- * @param {Object} config - Build config
95
- * @param {Object} bundleResult - Bundle result to cache
96
- */
97
- async cacheBundle(entryPath, config, bundleResult) {
98
- const cacheKey = this.computeCacheKey(entryPath, config);
99
- const cachePath = path.join(this.cacheDir, 'bundles', `${cacheKey}.json`);
100
-
101
- const cacheEntry = {
102
- hash: await this.hashFile(entryPath),
103
- timestamp: Date.now(),
104
- data: bundleResult
105
- };
106
-
107
- fs.writeFileSync(cachePath, JSON.stringify(cacheEntry, null, 2));
108
- }
109
-
110
- /**
111
- * Get cached native module build
112
- * @param {string} moduleRoot - Module root path
113
- * @param {string} target - Build target
114
- * @returns {string|null} - Path to cached .node file
115
- */
116
- getCachedNativeBuild(moduleRoot, target) {
117
- const cacheKey = crypto
118
- .createHash('sha256')
119
- .update(moduleRoot + target)
120
- .digest('hex')
121
- .substring(0, 16);
122
-
123
- const cachePath = path.join(this.cacheDir, 'natives', `${cacheKey}.node`);
124
-
125
- if (fs.existsSync(cachePath)) {
126
- // Check if source has changed
127
- const bindingGypPath = path.join(moduleRoot, 'binding.gyp');
128
- if (fs.existsSync(bindingGypPath)) {
129
- const cacheStats = fs.statSync(cachePath);
130
- const sourceStats = fs.statSync(bindingGypPath);
131
-
132
- if (cacheStats.mtime > sourceStats.mtime) {
133
- return cachePath;
134
- }
135
- }
136
- }
137
-
138
- return null;
139
- }
140
-
141
- /**
142
- * Cache a native module build
143
- * @param {string} moduleRoot - Module root path
144
- * @param {string} target - Build target
145
- * @param {string} builtBinaryPath - Path to built .node file
146
- */
147
- cacheNativeBuild(moduleRoot, target, builtBinaryPath) {
148
- const cacheKey = crypto
149
- .createHash('sha256')
150
- .update(moduleRoot + target)
151
- .digest('hex')
152
- .substring(0, 16);
153
-
154
- const cachePath = path.join(this.cacheDir, 'natives', `${cacheKey}.node`);
155
-
156
- fs.copyFileSync(builtBinaryPath, cachePath);
157
- }
158
-
159
- /**
160
- * Check if cache entry is valid
161
- * @param {CacheEntry} cached - Cached entry
162
- * @param {string} filePath - Source file path
163
- * @returns {Promise<boolean>}
164
- */
165
- async isValid(cached, filePath) {
166
- if (!fs.existsSync(filePath)) {
167
- return false;
168
- }
169
-
170
- const currentHash = await this.hashFile(filePath);
171
- return currentHash === cached.hash;
172
- }
173
-
174
- /**
175
- * Compute hash of a file
176
- * @param {string} filePath - File path
177
- * @returns {Promise<string>}
178
- */
179
- hashFile(filePath) {
180
- return new Promise((resolve, reject) => {
181
- const hash = crypto.createHash('sha256');
182
- const stream = fs.createReadStream(filePath);
183
-
184
- stream.on('data', chunk => hash.update(chunk));
185
- stream.on('end', () => resolve(hash.digest('hex')));
186
- stream.on('error', reject);
187
- });
188
- }
189
-
190
- /**
191
- * Clear cache
192
- */
193
- clear() {
194
- if (fs.existsSync(this.cacheDir)) {
195
- fs.rmSync(this.cacheDir, { recursive: true, force: true });
196
- }
197
- this.ensureCacheDir();
198
- }
199
- }
1
+ /**
2
+ * build-cache.mjs
3
+ * Build caching system to speed up incremental builds.
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import crypto from 'crypto';
9
+
10
+ /**
11
+ * @typedef {Object} CacheEntry
12
+ * @property {string} hash - Content hash of input
13
+ * @property {number} timestamp - When cached
14
+ * @property {any} data - Cached data
15
+ */
16
+
17
+ export class BuildCache {
18
+ /**
19
+ * @param {string} cacheDir - Cache directory path
20
+ */
21
+ constructor(cacheDir = '.seabox-cache') {
22
+ this.cacheDir = cacheDir;
23
+ this.ensureCacheDir();
24
+ }
25
+
26
+ /**
27
+ * Ensure cache directory exists
28
+ */
29
+ ensureCacheDir() {
30
+ if (!fs.existsSync(this.cacheDir)) {
31
+ fs.mkdirSync(this.cacheDir, { recursive: true });
32
+ }
33
+
34
+ // Create subdirectories
35
+ const subdirs = ['bundles', 'natives', 'blobs'];
36
+ for (const subdir of subdirs) {
37
+ const dirPath = path.join(this.cacheDir, subdir);
38
+ if (!fs.existsSync(dirPath)) {
39
+ fs.mkdirSync(dirPath, { recursive: true });
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Compute cache key from entry path and config
46
+ * @param {string} entryPath - Entry file path
47
+ * @param {Object} config - Build config
48
+ * @returns {string}
49
+ */
50
+ computeCacheKey(entryPath, config) {
51
+ const configStr = JSON.stringify({
52
+ bundler: config.bundler,
53
+ entry: entryPath
54
+ });
55
+
56
+ return crypto
57
+ .createHash('sha256')
58
+ .update(configStr)
59
+ .digest('hex')
60
+ .substring(0, 16);
61
+ }
62
+
63
+ /**
64
+ * Get cached bundle if valid
65
+ * @param {string} entryPath - Entry file path
66
+ * @param {Object} config - Build config
67
+ * @returns {Object|null}
68
+ */
69
+ async getCachedBundle(entryPath, config) {
70
+ const cacheKey = this.computeCacheKey(entryPath, config);
71
+ const cachePath = path.join(this.cacheDir, 'bundles', `${cacheKey}.json`);
72
+
73
+ if (!fs.existsSync(cachePath)) {
74
+ return null;
75
+ }
76
+
77
+ try {
78
+ const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
79
+
80
+ // Validate that source files haven't changed
81
+ if (await this.isValid(cached, entryPath)) {
82
+ return cached;
83
+ }
84
+ } catch (err) {
85
+ // Invalid cache entry
86
+ }
87
+
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Cache a bundle result
93
+ * @param {string} entryPath - Entry file path
94
+ * @param {Object} config - Build config
95
+ * @param {Object} bundleResult - Bundle result to cache
96
+ */
97
+ async cacheBundle(entryPath, config, bundleResult) {
98
+ const cacheKey = this.computeCacheKey(entryPath, config);
99
+ const cachePath = path.join(this.cacheDir, 'bundles', `${cacheKey}.json`);
100
+
101
+ const cacheEntry = {
102
+ hash: await this.hashFile(entryPath),
103
+ timestamp: Date.now(),
104
+ data: bundleResult
105
+ };
106
+
107
+ fs.writeFileSync(cachePath, JSON.stringify(cacheEntry, null, 2));
108
+ }
109
+
110
+ /**
111
+ * Get cached native module build
112
+ * @param {string} moduleRoot - Module root path
113
+ * @param {string} target - Build target
114
+ * @returns {string|null} - Path to cached .node file
115
+ */
116
+ getCachedNativeBuild(moduleRoot, target) {
117
+ const cacheKey = crypto
118
+ .createHash('sha256')
119
+ .update(moduleRoot + target)
120
+ .digest('hex')
121
+ .substring(0, 16);
122
+
123
+ const cachePath = path.join(this.cacheDir, 'natives', `${cacheKey}.node`);
124
+
125
+ if (fs.existsSync(cachePath)) {
126
+ // Check if source has changed
127
+ const bindingGypPath = path.join(moduleRoot, 'binding.gyp');
128
+ if (fs.existsSync(bindingGypPath)) {
129
+ const cacheStats = fs.statSync(cachePath);
130
+ const sourceStats = fs.statSync(bindingGypPath);
131
+
132
+ if (cacheStats.mtime > sourceStats.mtime) {
133
+ return cachePath;
134
+ }
135
+ }
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ /**
142
+ * Cache a native module build
143
+ * @param {string} moduleRoot - Module root path
144
+ * @param {string} target - Build target
145
+ * @param {string} builtBinaryPath - Path to built .node file
146
+ */
147
+ cacheNativeBuild(moduleRoot, target, builtBinaryPath) {
148
+ const cacheKey = crypto
149
+ .createHash('sha256')
150
+ .update(moduleRoot + target)
151
+ .digest('hex')
152
+ .substring(0, 16);
153
+
154
+ const cachePath = path.join(this.cacheDir, 'natives', `${cacheKey}.node`);
155
+
156
+ fs.copyFileSync(builtBinaryPath, cachePath);
157
+ }
158
+
159
+ /**
160
+ * Check if cache entry is valid
161
+ * @param {CacheEntry} cached - Cached entry
162
+ * @param {string} filePath - Source file path
163
+ * @returns {Promise<boolean>}
164
+ */
165
+ async isValid(cached, filePath) {
166
+ if (!fs.existsSync(filePath)) {
167
+ return false;
168
+ }
169
+
170
+ const currentHash = await this.hashFile(filePath);
171
+ return currentHash === cached.hash;
172
+ }
173
+
174
+ /**
175
+ * Compute hash of a file
176
+ * @param {string} filePath - File path
177
+ * @returns {Promise<string>}
178
+ */
179
+ hashFile(filePath) {
180
+ return new Promise((resolve, reject) => {
181
+ const hash = crypto.createHash('sha256');
182
+ const stream = fs.createReadStream(filePath);
183
+
184
+ stream.on('data', chunk => hash.update(chunk));
185
+ stream.on('end', () => resolve(hash.digest('hex')));
186
+ stream.on('error', reject);
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Clear cache
192
+ */
193
+ clear() {
194
+ if (fs.existsSync(this.cacheDir)) {
195
+ fs.rmSync(this.cacheDir, { recursive: true, force: true });
196
+ }
197
+ this.ensureCacheDir();
198
+ }
199
+ }
package/lib/build.mjs CHANGED
@@ -1,77 +1,77 @@
1
- /**
2
- * build.mjs (v2)
3
- * Main build orchestrator using v2 architecture with multi-target support.
4
- */
5
-
6
- import { loadConfig } from './config.mjs';
7
- import { MultiTargetBuilder } from './multi-target-builder.mjs';
8
- import * as diag from './diagnostics.mjs';
9
- import fs from 'fs';
10
-
11
- /**
12
- * Main build function for v2
13
- * @param {Object} options - Build options
14
- * @param {string} [options.configPath] - Path to config file
15
- * @param {boolean} [options.verbose] - Enable verbose logging
16
- * @param {boolean} [options.debug] - Keep temporary build files
17
- * @param {string} [options.projectRoot] - Project root directory
18
- */
19
- export async function build(options = {}) {
20
- // Support both options object and legacy process.argv parsing
21
- let configPath = options.configPath;
22
- let verbose = options.verbose;
23
- let debug = options.debug;
24
- let projectRoot = options.projectRoot || process.cwd();
25
-
26
- // Fallback to process.argv if called without options
27
- if (!options.configPath && !options.verbose && !options.debug) {
28
- const args = process.argv.slice(2);
29
- configPath = args.includes('--config') ? args[args.indexOf('--config') + 1] : null;
30
- verbose = args.includes('--verbose');
31
- debug = args.includes('--debug');
32
- }
33
-
34
- try {
35
- // Load configuration
36
- const config = loadConfig(configPath, projectRoot);
37
-
38
- // Config should exist if we got here (CLI checks first)
39
- if (!config) {
40
- throw new Error('No configuration found. Run: npx seabox init');
41
- }
42
-
43
- // Override verbose from CLI if specified
44
- if (verbose) {
45
- config.verbose = true;
46
- }
47
-
48
- // Create and run multi-target builder
49
- const builder = new MultiTargetBuilder(config, projectRoot);
50
- const results = await builder.buildAll();
51
-
52
- // Display results
53
- diag.separator();
54
- diag.info('Output files:');
55
- for (const result of results) {
56
- const size = diag.formatSize(fs.statSync(result.path).size);
57
- diag.info(` ${result.target}: ${result.path} (${size})`);
58
- }
59
- diag.separator();
60
-
61
- return results;
62
- } catch (error) {
63
- diag.separator();
64
- diag.error(`Build failed: ${error.message}`);
65
- if (verbose || process.argv.includes('--verbose')) {
66
- console.error(error.stack);
67
- }
68
- throw error;
69
- }
70
- }
71
-
72
- // Run if called directly
73
- if (import.meta.url === `file://${process.argv[1]}`) {
74
- build().catch(error => {
75
- process.exit(1);
76
- });
77
- }
1
+ /**
2
+ * build.mjs (v2)
3
+ * Main build orchestrator using v2 architecture with multi-target support.
4
+ */
5
+
6
+ import { loadConfig } from './config.mjs';
7
+ import { MultiTargetBuilder } from './multi-target-builder.mjs';
8
+ import * as diag from './diagnostics.mjs';
9
+ import fs from 'fs';
10
+
11
+ /**
12
+ * Main build function for v2
13
+ * @param {Object} options - Build options
14
+ * @param {string} [options.configPath] - Path to config file
15
+ * @param {boolean} [options.verbose] - Enable verbose logging
16
+ * @param {boolean} [options.debug] - Keep temporary build files
17
+ * @param {string} [options.projectRoot] - Project root directory
18
+ */
19
+ export async function build(options = {}) {
20
+ // Support both options object and legacy process.argv parsing
21
+ let configPath = options.configPath;
22
+ let verbose = options.verbose;
23
+ let debug = options.debug;
24
+ let projectRoot = options.projectRoot || process.cwd();
25
+
26
+ // Fallback to process.argv if called without options
27
+ if (!options.configPath && !options.verbose && !options.debug) {
28
+ const args = process.argv.slice(2);
29
+ configPath = args.includes('--config') ? args[args.indexOf('--config') + 1] : null;
30
+ verbose = args.includes('--verbose');
31
+ debug = args.includes('--debug');
32
+ }
33
+
34
+ try {
35
+ // Load configuration
36
+ const config = loadConfig(configPath, projectRoot);
37
+
38
+ // Config should exist if we got here (CLI checks first)
39
+ if (!config) {
40
+ throw new Error('No configuration found. Run: npx seabox init');
41
+ }
42
+
43
+ // Override verbose from CLI if specified
44
+ if (verbose) {
45
+ config.verbose = true;
46
+ }
47
+
48
+ // Create and run multi-target builder
49
+ const builder = new MultiTargetBuilder(config, projectRoot);
50
+ const results = await builder.buildAll();
51
+
52
+ // Display results
53
+ diag.separator();
54
+ diag.info('Output files:');
55
+ for (const result of results) {
56
+ const size = diag.formatSize(fs.statSync(result.path).size);
57
+ diag.info(` ${result.target}: ${result.path} (${size})`);
58
+ }
59
+ diag.separator();
60
+
61
+ return results;
62
+ } catch (error) {
63
+ diag.separator();
64
+ diag.error(`Build failed: ${error.message}`);
65
+ if (verbose || process.argv.includes('--verbose')) {
66
+ console.error(error.stack);
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ // Run if called directly
73
+ if (import.meta.url === `file://${process.argv[1]}`) {
74
+ build().catch(error => {
75
+ process.exit(1);
76
+ });
77
+ }