sitevision-cli 0.0.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.
Files changed (54) hide show
  1. package/dist/app.d.ts +7 -0
  2. package/dist/app.js +180 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +95 -0
  5. package/dist/commands/build.d.ts +12 -0
  6. package/dist/commands/build.js +168 -0
  7. package/dist/commands/deploy.d.ts +17 -0
  8. package/dist/commands/deploy.js +162 -0
  9. package/dist/commands/dev.d.ts +15 -0
  10. package/dist/commands/dev.js +291 -0
  11. package/dist/commands/index.d.ts +4 -0
  12. package/dist/commands/index.js +20 -0
  13. package/dist/commands/info.d.ts +2 -0
  14. package/dist/commands/info.js +66 -0
  15. package/dist/commands/setup-signing.d.ts +2 -0
  16. package/dist/commands/setup-signing.js +82 -0
  17. package/dist/commands/sign.d.ts +14 -0
  18. package/dist/commands/sign.js +103 -0
  19. package/dist/commands/types.d.ts +18 -0
  20. package/dist/commands/types.js +1 -0
  21. package/dist/components/DevPropertiesForm.d.ts +11 -0
  22. package/dist/components/DevPropertiesForm.js +87 -0
  23. package/dist/components/InfoScreen.d.ts +8 -0
  24. package/dist/components/InfoScreen.js +60 -0
  25. package/dist/components/MainMenu.d.ts +8 -0
  26. package/dist/components/MainMenu.js +138 -0
  27. package/dist/components/PasswordInput.d.ts +8 -0
  28. package/dist/components/PasswordInput.js +30 -0
  29. package/dist/components/ProcessOutput.d.ts +7 -0
  30. package/dist/components/ProcessOutput.js +32 -0
  31. package/dist/components/SetupFlow.d.ts +8 -0
  32. package/dist/components/SetupFlow.js +194 -0
  33. package/dist/components/SigningPropertiesForm.d.ts +8 -0
  34. package/dist/components/SigningPropertiesForm.js +49 -0
  35. package/dist/components/StatusIndicator.d.ts +9 -0
  36. package/dist/components/StatusIndicator.js +36 -0
  37. package/dist/components/TextInput.d.ts +11 -0
  38. package/dist/components/TextInput.js +37 -0
  39. package/dist/types/index.d.ts +250 -0
  40. package/dist/types/index.js +6 -0
  41. package/dist/utils/password-prompt.d.ts +4 -0
  42. package/dist/utils/password-prompt.js +45 -0
  43. package/dist/utils/process-runner.d.ts +30 -0
  44. package/dist/utils/process-runner.js +119 -0
  45. package/dist/utils/project-detection.d.ts +103 -0
  46. package/dist/utils/project-detection.js +287 -0
  47. package/dist/utils/sitevision-api.d.ts +56 -0
  48. package/dist/utils/sitevision-api.js +393 -0
  49. package/dist/utils/webpack-runner.d.ts +75 -0
  50. package/dist/utils/webpack-runner.js +313 -0
  51. package/dist/utils/zip.d.ts +64 -0
  52. package/dist/utils/zip.js +246 -0
  53. package/package.json +59 -0
  54. package/readme.md +196 -0
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Webpack Runner
3
+ *
4
+ * Handles webpack compilation for Sitevision apps.
5
+ * Dynamically loads webpack from the target project's node_modules.
6
+ */
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import { createRequire } from 'module';
10
+ import { EventEmitter } from 'events';
11
+ import { copyChunksToResources } from './zip.js';
12
+ // =============================================================================
13
+ // WEBPACK RUNNER CLASS
14
+ // =============================================================================
15
+ /**
16
+ * Events emitted by WebpackRunner:
17
+ * - 'compile': Emitted when compilation starts
18
+ * - 'done': Emitted when compilation completes successfully
19
+ * - 'error': Emitted when compilation fails
20
+ * - 'warning': Emitted when compilation has warnings
21
+ */
22
+ export class WebpackRunner extends EventEmitter {
23
+ constructor(projectRoot, options) {
24
+ super();
25
+ Object.defineProperty(this, "projectRoot", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: projectRoot
30
+ });
31
+ Object.defineProperty(this, "options", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: options
36
+ });
37
+ Object.defineProperty(this, "webpack", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: null
42
+ });
43
+ Object.defineProperty(this, "config", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: null
48
+ });
49
+ Object.defineProperty(this, "compiler", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: null
54
+ });
55
+ Object.defineProperty(this, "watcher", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: null
60
+ });
61
+ }
62
+ /**
63
+ * Initialize webpack by loading it from the project's node_modules
64
+ */
65
+ async initialize() {
66
+ if (this.webpack) {
67
+ return;
68
+ }
69
+ // Load webpack from project's node_modules
70
+ const webpackPath = path.join(this.projectRoot, 'node_modules', 'webpack');
71
+ if (!fs.existsSync(webpackPath)) {
72
+ throw new Error('webpack not found in project. Make sure webpack is installed: npm install webpack');
73
+ }
74
+ try {
75
+ // Use createRequire to load webpack (CommonJS module) from the project
76
+ const require = createRequire(path.join(this.projectRoot, 'package.json'));
77
+ this.webpack = require('webpack');
78
+ }
79
+ catch (error) {
80
+ throw new Error(`Failed to load webpack: ${error instanceof Error ? error.message : String(error)}`);
81
+ }
82
+ // Load webpack config from project
83
+ await this.loadConfig();
84
+ }
85
+ /**
86
+ * Load webpack configuration from the project
87
+ */
88
+ async loadConfig() {
89
+ // Try to find webpack config in standard locations
90
+ // Project-specific configs take priority, fall back to @sitevision/sitevision-scripts
91
+ const configPaths = [
92
+ path.join(this.projectRoot, 'webpack.config.js'),
93
+ path.join(this.projectRoot, 'webpack.config.mjs'),
94
+ path.join(this.projectRoot, 'config', 'webpack', 'webpack.config.js'),
95
+ path.join(this.projectRoot, 'node_modules', '@sitevision', 'sitevision-scripts', 'config', 'webpack', 'webpack.config.js'),
96
+ ];
97
+ let configPath = null;
98
+ for (const p of configPaths) {
99
+ if (fs.existsSync(p)) {
100
+ configPath = p;
101
+ break;
102
+ }
103
+ }
104
+ if (!configPath) {
105
+ throw new Error('webpack.config.js not found. Make sure your project has a webpack configuration.');
106
+ }
107
+ try {
108
+ const configModule = await import(configPath);
109
+ const configFactory = configModule.default || configModule;
110
+ // If it's a function, call it with options
111
+ if (typeof configFactory === 'function') {
112
+ this.config = configFactory({
113
+ dev: this.options.mode === 'development',
114
+ cssPrefix: this.options.cssPrefix || '',
115
+ restApp: this.options.restApp || false,
116
+ });
117
+ }
118
+ else {
119
+ this.config = configFactory;
120
+ }
121
+ // Override mode
122
+ this.config.mode = this.options.mode === 'development' ? 'development' : 'production';
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Failed to load webpack config: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+ /**
129
+ * Convert webpack stats to BuildResult
130
+ */
131
+ statsToResult(stats) {
132
+ const json = stats.toJson();
133
+ return {
134
+ success: !stats.hasErrors(),
135
+ outputPath: this.config?.output?.path,
136
+ errors: json.errors?.map((e) => e.message) || [],
137
+ warnings: json.warnings?.map((w) => w.message) || [],
138
+ stats: {
139
+ time: json.time || 0,
140
+ hash: json.hash || '',
141
+ assets: json.assets?.map((a) => a.name) || [],
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Run a single webpack build
147
+ */
148
+ async run() {
149
+ await this.initialize();
150
+ if (!this.webpack || !this.config) {
151
+ throw new Error('Webpack not initialized');
152
+ }
153
+ return new Promise((resolve, reject) => {
154
+ this.emit('compile');
155
+ this.compiler = this.webpack(this.config);
156
+ this.compiler.run((err, stats) => {
157
+ if (err) {
158
+ this.emit('error', err);
159
+ reject(err);
160
+ return;
161
+ }
162
+ if (!stats) {
163
+ const error = new Error('No stats returned from webpack');
164
+ this.emit('error', error);
165
+ reject(error);
166
+ return;
167
+ }
168
+ const result = this.statsToResult(stats);
169
+ // Copy chunks to resources if build succeeded
170
+ if (result.success && this.config?.output?.path) {
171
+ try {
172
+ copyChunksToResources(this.config.output.path);
173
+ }
174
+ catch (chunkError) {
175
+ // Non-fatal, just log
176
+ console.warn('Warning: Failed to copy chunks:', chunkError);
177
+ }
178
+ }
179
+ if (stats.hasErrors()) {
180
+ this.emit('error', new Error(stats.toString({ colors: false })));
181
+ }
182
+ else if (stats.hasWarnings()) {
183
+ this.emit('warning', stats.toString({ colors: false }));
184
+ }
185
+ this.emit('done', result);
186
+ resolve(result);
187
+ });
188
+ });
189
+ }
190
+ /**
191
+ * Start webpack in watch mode
192
+ *
193
+ * @param callback - Called after each compilation
194
+ */
195
+ async watch(callback) {
196
+ await this.initialize();
197
+ if (!this.webpack || !this.config) {
198
+ throw new Error('Webpack not initialized');
199
+ }
200
+ return new Promise((resolve) => {
201
+ this.compiler = this.webpack(this.config);
202
+ this.watcher = this.compiler.watch({
203
+ aggregateTimeout: 300,
204
+ ignored: ['**/dist/**', '**/build/**', '**/node_modules/**'],
205
+ }, (err, stats) => {
206
+ if (err) {
207
+ this.emit('error', err);
208
+ callback?.({
209
+ success: false,
210
+ errors: [err.message],
211
+ });
212
+ return;
213
+ }
214
+ if (!stats) {
215
+ return;
216
+ }
217
+ const result = this.statsToResult(stats);
218
+ // Copy chunks to resources if build succeeded
219
+ if (result.success && this.config?.output?.path) {
220
+ try {
221
+ copyChunksToResources(this.config.output.path);
222
+ }
223
+ catch (chunkError) {
224
+ console.warn('Warning: Failed to copy chunks:', chunkError);
225
+ }
226
+ }
227
+ if (stats.hasErrors()) {
228
+ this.emit('error', new Error(stats.toString({ colors: false })));
229
+ }
230
+ else if (stats.hasWarnings()) {
231
+ this.emit('warning', stats.toString({ colors: false }));
232
+ }
233
+ this.emit('done', result);
234
+ callback?.(result);
235
+ });
236
+ // Resolve immediately - watch continues in background
237
+ resolve();
238
+ });
239
+ }
240
+ /**
241
+ * Stop watching and close the compiler
242
+ */
243
+ async close() {
244
+ return new Promise((resolve) => {
245
+ if (this.watcher) {
246
+ this.watcher.close(() => {
247
+ this.watcher = null;
248
+ this.compiler = null;
249
+ resolve();
250
+ });
251
+ }
252
+ else if (this.compiler) {
253
+ this.compiler.close(() => {
254
+ this.compiler = null;
255
+ resolve();
256
+ });
257
+ }
258
+ else {
259
+ resolve();
260
+ }
261
+ });
262
+ }
263
+ /**
264
+ * Check if webpack is available in the project
265
+ */
266
+ static isWebpackAvailable(projectRoot) {
267
+ const webpackPath = path.join(projectRoot, 'node_modules', 'webpack');
268
+ return fs.existsSync(webpackPath);
269
+ }
270
+ /**
271
+ * Get the webpack version from the project
272
+ */
273
+ static getWebpackVersion(projectRoot) {
274
+ try {
275
+ const packageJsonPath = path.join(projectRoot, 'node_modules', 'webpack', 'package.json');
276
+ if (fs.existsSync(packageJsonPath)) {
277
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
278
+ return packageJson.version;
279
+ }
280
+ }
281
+ catch {
282
+ // Ignore errors
283
+ }
284
+ return null;
285
+ }
286
+ }
287
+ // =============================================================================
288
+ // HELPER FUNCTIONS
289
+ // =============================================================================
290
+ /**
291
+ * Run a single webpack build
292
+ *
293
+ * @param projectRoot - Project root directory
294
+ * @param options - Build options
295
+ * @returns Build result
296
+ */
297
+ export async function runWebpackBuild(projectRoot, options) {
298
+ const runner = new WebpackRunner(projectRoot, options);
299
+ return runner.run();
300
+ }
301
+ /**
302
+ * Start webpack in watch mode
303
+ *
304
+ * @param projectRoot - Project root directory
305
+ * @param options - Build options
306
+ * @param callback - Called after each compilation
307
+ * @returns WebpackRunner instance (call .close() to stop)
308
+ */
309
+ export async function startWebpackWatch(projectRoot, options, callback) {
310
+ const runner = new WebpackRunner(projectRoot, options);
311
+ await runner.watch(callback);
312
+ return runner;
313
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Zip Utilities
3
+ *
4
+ * Handles creating zip archives for Sitevision app distribution.
5
+ * Also handles webpack chunk organization.
6
+ */
7
+ /**
8
+ * Create a zip archive of a directory
9
+ *
10
+ * Uses the system `zip` command for cross-platform compatibility.
11
+ * Falls back to a basic implementation if zip is not available.
12
+ *
13
+ * @param sourceDir - Directory to zip
14
+ * @param outputPath - Path for the output zip file
15
+ * @returns Promise resolving to the output path
16
+ */
17
+ export declare function createZip(sourceDir: string, outputPath: string): Promise<string>;
18
+ /**
19
+ * Create a zip of the build directory for deployment
20
+ *
21
+ * @param projectRoot - Project root directory
22
+ * @param manifest - App manifest for naming
23
+ * @returns Path to the created zip file
24
+ */
25
+ export declare function createBuildZip(projectRoot: string, appId: string): Promise<string>;
26
+ /**
27
+ * Copy webpack chunks to the resource directory
28
+ *
29
+ * Webpack outputs chunk files (chunk-*.js) in the build directory.
30
+ * These need to be moved to a resource/ subdirectory for Sitevision.
31
+ *
32
+ * @param buildDir - The build directory containing webpack output
33
+ */
34
+ export declare function copyChunksToResources(buildDir: string): void;
35
+ /**
36
+ * Copy static files to build directory
37
+ *
38
+ * @param projectRoot - Project root directory
39
+ */
40
+ export declare function copyStaticToBuild(projectRoot: string): void;
41
+ /**
42
+ * Copy source files to build directory (for non-bundled apps)
43
+ *
44
+ * @param projectRoot - Project root directory
45
+ */
46
+ export declare function copySrcToBuild(projectRoot: string): void;
47
+ /**
48
+ * Clean the build directory
49
+ *
50
+ * @param projectRoot - Project root directory
51
+ */
52
+ export declare function cleanBuild(projectRoot: string): void;
53
+ /**
54
+ * Check if a zip file exists
55
+ */
56
+ export declare function zipExists(zipPath: string): boolean;
57
+ /**
58
+ * Get zip file size in bytes
59
+ */
60
+ export declare function getZipSize(zipPath: string): number;
61
+ /**
62
+ * Format file size for display
63
+ */
64
+ export declare function formatFileSize(bytes: number): string;
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Zip Utilities
3
+ *
4
+ * Handles creating zip archives for Sitevision app distribution.
5
+ * Also handles webpack chunk organization.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { spawn } from 'child_process';
10
+ import { ensureDistDir } from './project-detection.js';
11
+ // =============================================================================
12
+ // ZIP CREATION
13
+ // =============================================================================
14
+ /**
15
+ * Create a zip archive of a directory
16
+ *
17
+ * Uses the system `zip` command for cross-platform compatibility.
18
+ * Falls back to a basic implementation if zip is not available.
19
+ *
20
+ * @param sourceDir - Directory to zip
21
+ * @param outputPath - Path for the output zip file
22
+ * @returns Promise resolving to the output path
23
+ */
24
+ export async function createZip(sourceDir, outputPath) {
25
+ // Ensure the output directory exists
26
+ const outputDir = path.dirname(outputPath);
27
+ if (!fs.existsSync(outputDir)) {
28
+ fs.mkdirSync(outputDir, { recursive: true });
29
+ }
30
+ // Remove existing zip if present
31
+ if (fs.existsSync(outputPath)) {
32
+ fs.unlinkSync(outputPath);
33
+ }
34
+ return new Promise((resolve, reject) => {
35
+ // Try using system zip command (works on macOS, Linux, and Windows with Git Bash)
36
+ const zipProcess = spawn('zip', ['-r', outputPath, '.'], {
37
+ cwd: sourceDir,
38
+ stdio: ['ignore', 'pipe', 'pipe'],
39
+ });
40
+ let stderr = '';
41
+ zipProcess.stderr?.on('data', (data) => {
42
+ stderr += data.toString();
43
+ });
44
+ zipProcess.on('error', (error) => {
45
+ // If zip command not found, try alternative methods
46
+ if (error.code === 'ENOENT') {
47
+ // Fall back to tar on systems without zip
48
+ createZipWithTar(sourceDir, outputPath)
49
+ .then(resolve)
50
+ .catch(reject);
51
+ }
52
+ else {
53
+ reject(new Error(`Zip process error: ${error.message}`));
54
+ }
55
+ });
56
+ zipProcess.on('close', (code) => {
57
+ if (code === 0) {
58
+ resolve(outputPath);
59
+ }
60
+ else {
61
+ reject(new Error(`Zip failed with code ${code}: ${stderr}`));
62
+ }
63
+ });
64
+ });
65
+ }
66
+ /**
67
+ * Fallback: Create zip using tar (converts to zip format)
68
+ * This is a fallback for systems without the zip command.
69
+ */
70
+ async function createZipWithTar(sourceDir, outputPath) {
71
+ // On Windows without zip, we might need to use PowerShell
72
+ const isWindows = process.platform === 'win32';
73
+ if (isWindows) {
74
+ return createZipWithPowerShell(sourceDir, outputPath);
75
+ }
76
+ // On Unix without zip, this is unlikely but we'll throw an error
77
+ throw new Error('zip command not found. Please install zip: apt-get install zip (Linux) or brew install zip (macOS)');
78
+ }
79
+ /**
80
+ * Create zip using PowerShell on Windows
81
+ */
82
+ async function createZipWithPowerShell(sourceDir, outputPath) {
83
+ return new Promise((resolve, reject) => {
84
+ const absoluteSourceDir = path.resolve(sourceDir);
85
+ const absoluteOutputPath = path.resolve(outputPath);
86
+ const command = `Compress-Archive -Path "${absoluteSourceDir}\\*" -DestinationPath "${absoluteOutputPath}" -Force`;
87
+ const psProcess = spawn('powershell', ['-Command', command], {
88
+ stdio: ['ignore', 'pipe', 'pipe'],
89
+ });
90
+ let stderr = '';
91
+ psProcess.stderr?.on('data', (data) => {
92
+ stderr += data.toString();
93
+ });
94
+ psProcess.on('error', (error) => {
95
+ reject(new Error(`PowerShell error: ${error.message}`));
96
+ });
97
+ psProcess.on('close', (code) => {
98
+ if (code === 0) {
99
+ resolve(absoluteOutputPath);
100
+ }
101
+ else {
102
+ reject(new Error(`PowerShell zip failed with code ${code}: ${stderr}`));
103
+ }
104
+ });
105
+ });
106
+ }
107
+ /**
108
+ * Create a zip of the build directory for deployment
109
+ *
110
+ * @param projectRoot - Project root directory
111
+ * @param manifest - App manifest for naming
112
+ * @returns Path to the created zip file
113
+ */
114
+ export async function createBuildZip(projectRoot, appId) {
115
+ const buildDir = path.join(projectRoot, 'build');
116
+ const distDir = ensureDistDir(projectRoot);
117
+ const zipPath = path.join(distDir, `${appId}.zip`);
118
+ if (!fs.existsSync(buildDir)) {
119
+ throw new Error(`Build directory not found: ${buildDir}. Run build first.`);
120
+ }
121
+ return createZip(buildDir, zipPath);
122
+ }
123
+ // =============================================================================
124
+ // WEBPACK CHUNK UTILITIES
125
+ // =============================================================================
126
+ /**
127
+ * Copy webpack chunks to the resource directory
128
+ *
129
+ * Webpack outputs chunk files (chunk-*.js) in the build directory.
130
+ * These need to be moved to a resource/ subdirectory for Sitevision.
131
+ *
132
+ * @param buildDir - The build directory containing webpack output
133
+ */
134
+ export function copyChunksToResources(buildDir) {
135
+ const resourceDir = path.join(buildDir, 'resource');
136
+ // Create resource directory if it doesn't exist
137
+ if (!fs.existsSync(resourceDir)) {
138
+ fs.mkdirSync(resourceDir, { recursive: true });
139
+ }
140
+ // Find and move chunk files
141
+ const files = fs.readdirSync(buildDir);
142
+ const chunkPattern = /^chunk-.*\.js$/;
143
+ for (const file of files) {
144
+ if (chunkPattern.test(file)) {
145
+ const sourcePath = path.join(buildDir, file);
146
+ const destPath = path.join(resourceDir, file);
147
+ // Move file (copy then delete)
148
+ fs.copyFileSync(sourcePath, destPath);
149
+ fs.unlinkSync(sourcePath);
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * Copy static files to build directory
155
+ *
156
+ * @param projectRoot - Project root directory
157
+ */
158
+ export function copyStaticToBuild(projectRoot) {
159
+ const staticDir = path.join(projectRoot, 'static');
160
+ const buildDir = path.join(projectRoot, 'build');
161
+ if (!fs.existsSync(staticDir)) {
162
+ return;
163
+ }
164
+ // Ensure build directory exists
165
+ if (!fs.existsSync(buildDir)) {
166
+ fs.mkdirSync(buildDir, { recursive: true });
167
+ }
168
+ // Copy static directory contents to build
169
+ copyDirRecursive(staticDir, buildDir);
170
+ }
171
+ /**
172
+ * Copy source files to build directory (for non-bundled apps)
173
+ *
174
+ * @param projectRoot - Project root directory
175
+ */
176
+ export function copySrcToBuild(projectRoot) {
177
+ const srcDir = path.join(projectRoot, 'src');
178
+ const buildDir = path.join(projectRoot, 'build');
179
+ if (!fs.existsSync(srcDir)) {
180
+ return;
181
+ }
182
+ // Ensure build directory exists
183
+ if (!fs.existsSync(buildDir)) {
184
+ fs.mkdirSync(buildDir, { recursive: true });
185
+ }
186
+ // Copy src directory contents to build
187
+ copyDirRecursive(srcDir, buildDir);
188
+ }
189
+ /**
190
+ * Recursively copy a directory
191
+ */
192
+ function copyDirRecursive(src, dest) {
193
+ if (!fs.existsSync(dest)) {
194
+ fs.mkdirSync(dest, { recursive: true });
195
+ }
196
+ const entries = fs.readdirSync(src, { withFileTypes: true });
197
+ for (const entry of entries) {
198
+ const srcPath = path.join(src, entry.name);
199
+ const destPath = path.join(dest, entry.name);
200
+ if (entry.isDirectory()) {
201
+ copyDirRecursive(srcPath, destPath);
202
+ }
203
+ else {
204
+ fs.copyFileSync(srcPath, destPath);
205
+ }
206
+ }
207
+ }
208
+ /**
209
+ * Clean the build directory
210
+ *
211
+ * @param projectRoot - Project root directory
212
+ */
213
+ export function cleanBuild(projectRoot) {
214
+ const buildDir = path.join(projectRoot, 'build');
215
+ if (fs.existsSync(buildDir)) {
216
+ fs.rmSync(buildDir, { recursive: true, force: true });
217
+ }
218
+ }
219
+ /**
220
+ * Check if a zip file exists
221
+ */
222
+ export function zipExists(zipPath) {
223
+ return fs.existsSync(zipPath);
224
+ }
225
+ /**
226
+ * Get zip file size in bytes
227
+ */
228
+ export function getZipSize(zipPath) {
229
+ if (!fs.existsSync(zipPath)) {
230
+ return 0;
231
+ }
232
+ const stats = fs.statSync(zipPath);
233
+ return stats.size;
234
+ }
235
+ /**
236
+ * Format file size for display
237
+ */
238
+ export function formatFileSize(bytes) {
239
+ if (bytes === 0) {
240
+ return '0 B';
241
+ }
242
+ const units = ['B', 'KB', 'MB', 'GB'];
243
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
244
+ const size = bytes / Math.pow(1024, i);
245
+ return `${size.toFixed(i > 0 ? 2 : 0)} ${units[i]}`;
246
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "sitevision-cli",
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "bin": {
6
+ "svc": "dist/cli.js"
7
+ },
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=16"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "test": "prettier --check . && xo && ava"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "ink": "^6.6.0",
22
+ "ink-spinner": "^5.0.0",
23
+ "meow": "^11.0.0",
24
+ "react": "^19.2.3"
25
+ },
26
+ "devDependencies": {
27
+ "@sindresorhus/tsconfig": "^3.0.1",
28
+ "@types/node": "^25.0.3",
29
+ "@types/react": "^19.2.7",
30
+ "@vdemedes/prettier-config": "^2.0.1",
31
+ "ava": "^5.2.0",
32
+ "chalk": "^5.2.0",
33
+ "eslint-config-xo-react": "^0.27.0",
34
+ "eslint-plugin-react": "^7.32.2",
35
+ "eslint-plugin-react-hooks": "^4.6.0",
36
+ "ink-testing-library": "^3.0.0",
37
+ "prettier": "^2.8.7",
38
+ "ts-node": "^10.9.1",
39
+ "typescript": "^5.0.3",
40
+ "xo": "^0.53.1"
41
+ },
42
+ "ava": {
43
+ "extensions": {
44
+ "ts": "module",
45
+ "tsx": "module"
46
+ },
47
+ "nodeArguments": [
48
+ "--loader=ts-node/esm"
49
+ ]
50
+ },
51
+ "xo": {
52
+ "extends": "xo-react",
53
+ "prettier": true,
54
+ "rules": {
55
+ "react/prop-types": "off"
56
+ }
57
+ },
58
+ "prettier": "@vdemedes/prettier-config"
59
+ }