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.
- package/dist/app.d.ts +7 -0
- package/dist/app.js +180 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +95 -0
- package/dist/commands/build.d.ts +12 -0
- package/dist/commands/build.js +168 -0
- package/dist/commands/deploy.d.ts +17 -0
- package/dist/commands/deploy.js +162 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +291 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +20 -0
- package/dist/commands/info.d.ts +2 -0
- package/dist/commands/info.js +66 -0
- package/dist/commands/setup-signing.d.ts +2 -0
- package/dist/commands/setup-signing.js +82 -0
- package/dist/commands/sign.d.ts +14 -0
- package/dist/commands/sign.js +103 -0
- package/dist/commands/types.d.ts +18 -0
- package/dist/commands/types.js +1 -0
- package/dist/components/DevPropertiesForm.d.ts +11 -0
- package/dist/components/DevPropertiesForm.js +87 -0
- package/dist/components/InfoScreen.d.ts +8 -0
- package/dist/components/InfoScreen.js +60 -0
- package/dist/components/MainMenu.d.ts +8 -0
- package/dist/components/MainMenu.js +138 -0
- package/dist/components/PasswordInput.d.ts +8 -0
- package/dist/components/PasswordInput.js +30 -0
- package/dist/components/ProcessOutput.d.ts +7 -0
- package/dist/components/ProcessOutput.js +32 -0
- package/dist/components/SetupFlow.d.ts +8 -0
- package/dist/components/SetupFlow.js +194 -0
- package/dist/components/SigningPropertiesForm.d.ts +8 -0
- package/dist/components/SigningPropertiesForm.js +49 -0
- package/dist/components/StatusIndicator.d.ts +9 -0
- package/dist/components/StatusIndicator.js +36 -0
- package/dist/components/TextInput.d.ts +11 -0
- package/dist/components/TextInput.js +37 -0
- package/dist/types/index.d.ts +250 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/password-prompt.d.ts +4 -0
- package/dist/utils/password-prompt.js +45 -0
- package/dist/utils/process-runner.d.ts +30 -0
- package/dist/utils/process-runner.js +119 -0
- package/dist/utils/project-detection.d.ts +103 -0
- package/dist/utils/project-detection.js +287 -0
- package/dist/utils/sitevision-api.d.ts +56 -0
- package/dist/utils/sitevision-api.js +393 -0
- package/dist/utils/webpack-runner.d.ts +75 -0
- package/dist/utils/webpack-runner.js +313 -0
- package/dist/utils/zip.d.ts +64 -0
- package/dist/utils/zip.js +246 -0
- package/package.json +59 -0
- 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
|
+
}
|