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
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { MainMenu } from './components/MainMenu.js';
|
|
3
|
+
import { InfoScreen } from './components/InfoScreen.js';
|
|
4
|
+
import { SetupFlow } from './components/SetupFlow.js';
|
|
5
|
+
import { PasswordInput } from './components/PasswordInput.js';
|
|
6
|
+
import { DevScreen } from './commands/dev.js';
|
|
7
|
+
import { BuildScreen } from './commands/build.js';
|
|
8
|
+
import { DeployScreen } from './commands/deploy.js';
|
|
9
|
+
import { SignScreen } from './commands/sign.js';
|
|
10
|
+
import { SigningPropertiesForm } from './components/SigningPropertiesForm.js';
|
|
11
|
+
export default function App({ project }) {
|
|
12
|
+
const [state, setState] = useState('setup');
|
|
13
|
+
const [currentCommand, setCurrentCommand] = useState('');
|
|
14
|
+
const [signingPassword, setSigningPassword] = useState('');
|
|
15
|
+
const [devPassword, setDevPassword] = useState('');
|
|
16
|
+
// Check if dev password is available (either from file or session)
|
|
17
|
+
const hasDevPassword = Boolean(project.devProperties?.password || devPassword);
|
|
18
|
+
// Get effective dev properties with session password if needed
|
|
19
|
+
const getEffectiveDevProperties = () => {
|
|
20
|
+
if (!project.devProperties)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (project.devProperties.password)
|
|
23
|
+
return project.devProperties;
|
|
24
|
+
return { ...project.devProperties, password: devPassword };
|
|
25
|
+
};
|
|
26
|
+
const handleDevPasswordSubmit = (password) => {
|
|
27
|
+
setDevPassword(password);
|
|
28
|
+
// Continue to the intended command
|
|
29
|
+
if (currentCommand === 'dev' || currentCommand === 'dev-signed') {
|
|
30
|
+
if (currentCommand === 'dev-signed' && !signingPassword) {
|
|
31
|
+
setState('signing-password-input');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
setState('dev');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (currentCommand.startsWith('deploy')) {
|
|
38
|
+
setState('deploy');
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const handleSigningPasswordSubmit = (password) => {
|
|
42
|
+
setSigningPassword(password);
|
|
43
|
+
if (currentCommand === 'dev-signed') {
|
|
44
|
+
setState('dev');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
setState('sign');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handleCommandSelect = (command) => {
|
|
51
|
+
setCurrentCommand(command);
|
|
52
|
+
switch (command) {
|
|
53
|
+
case 'info':
|
|
54
|
+
setState('info');
|
|
55
|
+
break;
|
|
56
|
+
case 'setup-signing':
|
|
57
|
+
setState('setup-signing');
|
|
58
|
+
break;
|
|
59
|
+
case 'dev':
|
|
60
|
+
if (!project.hasDevProperties || !project.devProperties) {
|
|
61
|
+
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!hasDevPassword) {
|
|
65
|
+
setState('dev-password-input');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
setState('dev');
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'dev-signed':
|
|
72
|
+
if (!project.hasDevProperties || !project.devProperties) {
|
|
73
|
+
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!project.hasSigningProperties) {
|
|
77
|
+
console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Need both dev password and signing password
|
|
81
|
+
if (!hasDevPassword) {
|
|
82
|
+
setState('dev-password-input');
|
|
83
|
+
}
|
|
84
|
+
else if (!signingPassword) {
|
|
85
|
+
setState('signing-password-input');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
setState('dev');
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case 'sign':
|
|
92
|
+
if (!project.hasDevProperties || !project.devProperties) {
|
|
93
|
+
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!project.hasSigningProperties) {
|
|
97
|
+
console.log('\x1b[31mSigning credentials not configured. Run svc setup-signing first.\x1b[0m');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (signingPassword) {
|
|
101
|
+
setState('sign');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
setState('signing-password-input');
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case 'build':
|
|
108
|
+
setState('build');
|
|
109
|
+
break;
|
|
110
|
+
case 'deploy':
|
|
111
|
+
case 'deploy-force':
|
|
112
|
+
case 'deploy-production':
|
|
113
|
+
if (!project.hasDevProperties || !project.devProperties) {
|
|
114
|
+
console.log('\x1b[31mDevelopment properties not configured. Create a .dev_properties.json file first.\x1b[0m');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!hasDevPassword) {
|
|
118
|
+
setState('dev-password-input');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
setState('deploy');
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
if (state === 'setup') {
|
|
127
|
+
return React.createElement(SetupFlow, { project: project, onComplete: () => setState('menu') });
|
|
128
|
+
}
|
|
129
|
+
if (state === 'menu') {
|
|
130
|
+
return React.createElement(MainMenu, { project: project, onSelect: handleCommandSelect });
|
|
131
|
+
}
|
|
132
|
+
if (state === 'info') {
|
|
133
|
+
return React.createElement(InfoScreen, { project: project, onBack: () => setState('menu') });
|
|
134
|
+
}
|
|
135
|
+
if (state === 'dev-password-input') {
|
|
136
|
+
return (React.createElement(PasswordInput, { key: "dev-password", label: "Enter Development Password (usually Sitevision Cloud Password)", onSubmit: handleDevPasswordSubmit, onCancel: () => setState('menu') }));
|
|
137
|
+
}
|
|
138
|
+
if (state === 'signing-password-input') {
|
|
139
|
+
return (React.createElement(PasswordInput, { key: "signing-password", label: "Enter Signing Password (developer.sitevision.se)", onSubmit: handleSigningPasswordSubmit, onCancel: () => setState('menu') }));
|
|
140
|
+
}
|
|
141
|
+
if (state === 'dev') {
|
|
142
|
+
return (React.createElement(DevScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: getEffectiveDevProperties(), signed: currentCommand === 'dev-signed', onBack: () => setState('menu'), onRetryCredentials: () => {
|
|
143
|
+
setDevPassword('');
|
|
144
|
+
if (currentCommand === 'dev-signed') {
|
|
145
|
+
setSigningPassword('');
|
|
146
|
+
}
|
|
147
|
+
setState('dev-password-input');
|
|
148
|
+
}, signingCredentials: currentCommand === 'dev-signed' && project.devProperties?.signingUsername
|
|
149
|
+
? {
|
|
150
|
+
username: project.devProperties.signingUsername,
|
|
151
|
+
password: signingPassword,
|
|
152
|
+
certificateName: project.devProperties.certificateName,
|
|
153
|
+
}
|
|
154
|
+
: undefined }));
|
|
155
|
+
}
|
|
156
|
+
if (state === 'build') {
|
|
157
|
+
return (React.createElement(BuildScreen, { projectRoot: project.root, manifest: project.manifest, createZip: true, onBack: () => setState('menu') }));
|
|
158
|
+
}
|
|
159
|
+
if (state === 'sign') {
|
|
160
|
+
return (React.createElement(SignScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, password: signingPassword, onBack: () => setState('menu'), onRetryCredentials: () => {
|
|
161
|
+
setSigningPassword('');
|
|
162
|
+
setState('signing-password-input');
|
|
163
|
+
} }));
|
|
164
|
+
}
|
|
165
|
+
if (state === 'deploy') {
|
|
166
|
+
return (React.createElement(DeployScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: getEffectiveDevProperties(), force: currentCommand === 'deploy-force', production: currentCommand === 'deploy-production', activate: currentCommand === 'deploy-production', onBack: () => setState('menu'), onRetryCredentials: () => {
|
|
167
|
+
setDevPassword('');
|
|
168
|
+
setState('dev-password-input');
|
|
169
|
+
} }));
|
|
170
|
+
}
|
|
171
|
+
if (state === 'setup-signing') {
|
|
172
|
+
return (React.createElement(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
|
|
173
|
+
// We can't easily update project info here without full reload,
|
|
174
|
+
// but since we are just returning to menu, it's fine.
|
|
175
|
+
// The user might need to restart CLI or we implement a reload mechanism.
|
|
176
|
+
setState('menu');
|
|
177
|
+
}, onCancel: () => setState('menu') }));
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import { Text, Box } from 'ink';
|
|
5
|
+
import meow from 'meow';
|
|
6
|
+
import App from './app.js';
|
|
7
|
+
import { getCommand } from './commands/index.js';
|
|
8
|
+
import { requireProject } from './utils/project-detection.js';
|
|
9
|
+
const cli = meow(`
|
|
10
|
+
Usage
|
|
11
|
+
$ svc Start interactive menu
|
|
12
|
+
$ svc <command> [options]
|
|
13
|
+
|
|
14
|
+
Commands
|
|
15
|
+
dev Start development server with watch mode
|
|
16
|
+
build Build the application for production
|
|
17
|
+
deploy Deploy the application
|
|
18
|
+
info Show project information
|
|
19
|
+
|
|
20
|
+
Options
|
|
21
|
+
--help Show this help message
|
|
22
|
+
--version Show version number
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
$ svc # Interactive menu
|
|
26
|
+
$ svc dev
|
|
27
|
+
$ svc dev --signed
|
|
28
|
+
$ svc build
|
|
29
|
+
$ svc deploy --force
|
|
30
|
+
$ svc deploy --production
|
|
31
|
+
$ svc info
|
|
32
|
+
`, {
|
|
33
|
+
importMeta: import.meta,
|
|
34
|
+
flags: {
|
|
35
|
+
signed: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
force: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
alias: 'f',
|
|
42
|
+
default: false,
|
|
43
|
+
},
|
|
44
|
+
production: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
alias: 'p',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const [commandName, ...args] = cli.input;
|
|
52
|
+
async function main() {
|
|
53
|
+
// Check if we're in a Sitevision project
|
|
54
|
+
const project = (() => {
|
|
55
|
+
try {
|
|
56
|
+
return requireProject();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
render(React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
60
|
+
React.createElement(Text, { color: "red" },
|
|
61
|
+
"Error: ",
|
|
62
|
+
error.message),
|
|
63
|
+
React.createElement(Text, { dimColor: true }, "Make sure you're in a Sitevision project directory")));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
// If no command, show interactive menu
|
|
68
|
+
if (!commandName) {
|
|
69
|
+
render(React.createElement(App, { project: project }));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Get the command
|
|
73
|
+
const command = getCommand(commandName);
|
|
74
|
+
if (!command) {
|
|
75
|
+
render(React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
76
|
+
React.createElement(Text, { color: "red" },
|
|
77
|
+
"Unknown command: ",
|
|
78
|
+
commandName),
|
|
79
|
+
React.createElement(Text, null,
|
|
80
|
+
"Run ",
|
|
81
|
+
React.createElement(Text, { bold: true }, "svc --help"),
|
|
82
|
+
" to see available commands")));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
// Execute the command
|
|
86
|
+
await command.execute({
|
|
87
|
+
project,
|
|
88
|
+
flags: cli.flags,
|
|
89
|
+
args,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
main().catch((error) => {
|
|
93
|
+
console.error('Fatal error:', error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Command } from './types.js';
|
|
3
|
+
import type { SitevisionManifest } from '../types/index.js';
|
|
4
|
+
interface BuildScreenProps {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
manifest: SitevisionManifest;
|
|
7
|
+
createZip?: boolean;
|
|
8
|
+
onBack?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function BuildScreen({ projectRoot, manifest, createZip, onBack }: BuildScreenProps): React.JSX.Element;
|
|
11
|
+
export declare const buildCommand: Command;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
3
|
+
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
4
|
+
import { WebpackRunner } from '../utils/webpack-runner.js';
|
|
5
|
+
import { copyStaticToBuild, copySrcToBuild, cleanBuild, formatFileSize, createBuildZip, getZipSize, } from '../utils/zip.js';
|
|
6
|
+
import { isBundledApp, getAppType, getFullAppId, } from '../utils/project-detection.js';
|
|
7
|
+
export function BuildScreen({ projectRoot, manifest, createZip = true, onBack }) {
|
|
8
|
+
const [state, setState] = React.useState({
|
|
9
|
+
status: 'cleaning',
|
|
10
|
+
message: 'Cleaning build directory...',
|
|
11
|
+
});
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (onBack && (key.escape || input === 'q') && (state.status === 'success' || state.status === 'error')) {
|
|
14
|
+
onBack();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
const isBundled = isBundledApp(manifest);
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
async function runBuild() {
|
|
20
|
+
try {
|
|
21
|
+
// Step 1: Clean build directory
|
|
22
|
+
setState({ status: 'cleaning', message: 'Cleaning build directory...' });
|
|
23
|
+
cleanBuild(projectRoot);
|
|
24
|
+
// Step 2: Build or copy files
|
|
25
|
+
if (isBundled) {
|
|
26
|
+
// Check if webpack is available
|
|
27
|
+
if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
|
|
28
|
+
setState({
|
|
29
|
+
status: 'error',
|
|
30
|
+
error: 'webpack not found. Run npm install to install dependencies.',
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setState({ status: 'building', message: 'Compiling with webpack...' });
|
|
35
|
+
const appType = getAppType(manifest);
|
|
36
|
+
const runner = new WebpackRunner(projectRoot, {
|
|
37
|
+
mode: 'production',
|
|
38
|
+
cssPrefix: manifest.id,
|
|
39
|
+
restApp: appType === 'rest',
|
|
40
|
+
});
|
|
41
|
+
const result = await runner.run();
|
|
42
|
+
await runner.close();
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
setState({
|
|
45
|
+
status: 'error',
|
|
46
|
+
error: result.errors?.join('\n') || 'Build failed',
|
|
47
|
+
result,
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Copy static files after webpack build
|
|
52
|
+
setState({ status: 'copying', message: 'Copying static files...' });
|
|
53
|
+
copyStaticToBuild(projectRoot);
|
|
54
|
+
setState(prev => ({ ...prev, result }));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Non-bundled app: just copy files
|
|
58
|
+
setState({ status: 'copying', message: 'Copying source files...' });
|
|
59
|
+
copySrcToBuild(projectRoot);
|
|
60
|
+
copyStaticToBuild(projectRoot);
|
|
61
|
+
}
|
|
62
|
+
// Step 3: Create zip if requested
|
|
63
|
+
if (createZip) {
|
|
64
|
+
setState({ status: 'zipping', message: 'Creating zip archive...' });
|
|
65
|
+
const appId = getFullAppId(manifest.id);
|
|
66
|
+
const zipPath = await createBuildZip(projectRoot, appId);
|
|
67
|
+
const zipSize = getZipSize(zipPath);
|
|
68
|
+
setState(prev => ({
|
|
69
|
+
...prev,
|
|
70
|
+
status: 'success',
|
|
71
|
+
message: 'Build complete',
|
|
72
|
+
zipPath,
|
|
73
|
+
zipSize,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
setState(prev => ({
|
|
78
|
+
...prev,
|
|
79
|
+
status: 'success',
|
|
80
|
+
message: 'Build complete',
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
setState({
|
|
86
|
+
status: 'error',
|
|
87
|
+
error: error instanceof Error ? error.message : String(error),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
runBuild();
|
|
92
|
+
}, [projectRoot, manifest, isBundled, createZip]);
|
|
93
|
+
const getStatusIndicator = () => {
|
|
94
|
+
switch (state.status) {
|
|
95
|
+
case 'cleaning':
|
|
96
|
+
case 'building':
|
|
97
|
+
case 'copying':
|
|
98
|
+
case 'zipping':
|
|
99
|
+
return 'running';
|
|
100
|
+
case 'success':
|
|
101
|
+
return 'success';
|
|
102
|
+
case 'error':
|
|
103
|
+
return 'error';
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const getStatusLabel = () => {
|
|
107
|
+
switch (state.status) {
|
|
108
|
+
case 'cleaning':
|
|
109
|
+
return 'Cleaning';
|
|
110
|
+
case 'building':
|
|
111
|
+
return 'Building';
|
|
112
|
+
case 'copying':
|
|
113
|
+
return 'Copying files';
|
|
114
|
+
case 'zipping':
|
|
115
|
+
return 'Creating zip';
|
|
116
|
+
case 'success':
|
|
117
|
+
return 'Build complete';
|
|
118
|
+
case 'error':
|
|
119
|
+
return 'Build failed';
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
123
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
124
|
+
React.createElement(StatusIndicator, { status: getStatusIndicator(), label: getStatusLabel(), message: state.message })),
|
|
125
|
+
state.status === 'success' && state.result?.stats && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
126
|
+
React.createElement(Text, { dimColor: true },
|
|
127
|
+
"Compiled in ",
|
|
128
|
+
state.result.stats.time,
|
|
129
|
+
"ms"),
|
|
130
|
+
state.result.stats.assets && state.result.stats.assets.length > 0 && (React.createElement(Text, { dimColor: true },
|
|
131
|
+
"Assets: ",
|
|
132
|
+
state.result.stats.assets.join(', '))))),
|
|
133
|
+
state.status === 'success' && state.zipPath && (React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1 },
|
|
134
|
+
React.createElement(Text, { color: "green" },
|
|
135
|
+
"\u2713 Created: ",
|
|
136
|
+
state.zipPath),
|
|
137
|
+
state.zipSize !== undefined && (React.createElement(Text, { dimColor: true },
|
|
138
|
+
" Size: ",
|
|
139
|
+
formatFileSize(state.zipSize))))),
|
|
140
|
+
state.result?.warnings && state.result.warnings.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
141
|
+
React.createElement(Text, { color: "yellow" }, "Warnings:"),
|
|
142
|
+
state.result.warnings.slice(0, 5).map((warning, i) => (React.createElement(Text, { key: i, color: "yellow", dimColor: true }, warning.substring(0, 200)))),
|
|
143
|
+
state.result.warnings.length > 5 && (React.createElement(Text, { color: "yellow", dimColor: true },
|
|
144
|
+
"...and ",
|
|
145
|
+
state.result.warnings.length - 5,
|
|
146
|
+
" more")))),
|
|
147
|
+
state.status === 'error' && state.error && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
148
|
+
React.createElement(Text, { color: "red" }, state.error))),
|
|
149
|
+
onBack && (state.status === 'success' || state.status === 'error') && (React.createElement(Box, { marginTop: 1 },
|
|
150
|
+
React.createElement(Text, { dimColor: true }, "Press q or Esc to return to menu")))));
|
|
151
|
+
}
|
|
152
|
+
export const buildCommand = {
|
|
153
|
+
name: 'build',
|
|
154
|
+
description: 'Build the application for production',
|
|
155
|
+
requiresProject: true,
|
|
156
|
+
flags: {
|
|
157
|
+
'no-zip': {
|
|
158
|
+
type: 'boolean',
|
|
159
|
+
description: 'Skip creating the zip archive',
|
|
160
|
+
default: false,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
async execute({ project, flags }) {
|
|
164
|
+
const createZip = !flags['no-zip'];
|
|
165
|
+
const { waitUntilExit } = render(React.createElement(BuildScreen, { projectRoot: project.root, manifest: project.manifest, createZip: createZip }));
|
|
166
|
+
await waitUntilExit();
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Command } from './types.js';
|
|
3
|
+
import type { SitevisionManifest, DevProperties } from '../types/index.js';
|
|
4
|
+
interface DeployScreenProps {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
manifest: SitevisionManifest;
|
|
7
|
+
devProperties: DevProperties;
|
|
8
|
+
force: boolean;
|
|
9
|
+
production: boolean;
|
|
10
|
+
activate: boolean;
|
|
11
|
+
signingPassword?: string;
|
|
12
|
+
onBack?: () => void;
|
|
13
|
+
onRetryCredentials?: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate, signingPassword, onBack, onRetryCredentials, }: DeployScreenProps): React.JSX.Element;
|
|
16
|
+
export declare const deployCommand: Command;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
3
|
+
import { StatusIndicator } from '../components/StatusIndicator.js';
|
|
4
|
+
import { deployApp, deployProduction } from '../utils/sitevision-api.js';
|
|
5
|
+
import { getZipPath, getSignedZipPath, getAppType, } from '../utils/project-detection.js';
|
|
6
|
+
import { zipExists } from '../utils/zip.js';
|
|
7
|
+
export function DeployScreen({ projectRoot, manifest, devProperties, force, production, activate, signingPassword, onBack, onRetryCredentials, }) {
|
|
8
|
+
const [state, setState] = React.useState({
|
|
9
|
+
status: 'deploying',
|
|
10
|
+
message: production ? 'Deploying to production...' : 'Deploying to dev...',
|
|
11
|
+
});
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (state.status !== 'deploying') {
|
|
14
|
+
if (onBack && (key.escape || input === 'q')) {
|
|
15
|
+
onBack();
|
|
16
|
+
}
|
|
17
|
+
if (onRetryCredentials && state.status === 'error' && input === 'r') {
|
|
18
|
+
onRetryCredentials();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
async function runDeploy() {
|
|
24
|
+
try {
|
|
25
|
+
const appType = getAppType(manifest);
|
|
26
|
+
if (production) {
|
|
27
|
+
// Production deployment requires a signed zip
|
|
28
|
+
const signedZipPath = getSignedZipPath(projectRoot, manifest);
|
|
29
|
+
if (!zipExists(signedZipPath)) {
|
|
30
|
+
setState({
|
|
31
|
+
status: 'error',
|
|
32
|
+
error: `Signed zip not found: ${signedZipPath}\nRun 'sign' first to create the signed zip.`,
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const config = {
|
|
37
|
+
domain: devProperties.domain,
|
|
38
|
+
siteName: devProperties.siteName,
|
|
39
|
+
addonName: devProperties.addonName,
|
|
40
|
+
username: devProperties.username,
|
|
41
|
+
password: devProperties.password,
|
|
42
|
+
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
43
|
+
activate,
|
|
44
|
+
};
|
|
45
|
+
const result = await deployProduction(signedZipPath, config, appType);
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
setState({
|
|
48
|
+
status: 'error',
|
|
49
|
+
error: result.error || 'Deployment failed',
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setState({
|
|
54
|
+
status: 'success',
|
|
55
|
+
message: result.message || 'Deployed to production successfully',
|
|
56
|
+
executableId: result.executableId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Dev deployment can use unsigned zip
|
|
61
|
+
const zipPath = getZipPath(projectRoot, manifest);
|
|
62
|
+
if (!zipExists(zipPath)) {
|
|
63
|
+
setState({
|
|
64
|
+
status: 'error',
|
|
65
|
+
error: `Zip not found: ${zipPath}\nRun 'build' first to create the zip.`,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const config = {
|
|
70
|
+
domain: devProperties.domain,
|
|
71
|
+
siteName: devProperties.siteName,
|
|
72
|
+
addonName: devProperties.addonName,
|
|
73
|
+
username: devProperties.username,
|
|
74
|
+
password: devProperties.password,
|
|
75
|
+
useHTTP: devProperties.useHTTPForDevDeploy,
|
|
76
|
+
};
|
|
77
|
+
const result = await deployApp(zipPath, config, appType, force);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
setState({
|
|
80
|
+
status: 'error',
|
|
81
|
+
error: result.error || 'Deployment failed',
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setState({
|
|
86
|
+
status: 'success',
|
|
87
|
+
message: 'Deployed to dev successfully',
|
|
88
|
+
executableId: result.executableId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
setState({
|
|
94
|
+
status: 'error',
|
|
95
|
+
error: error instanceof Error ? error.message : String(error),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
runDeploy();
|
|
100
|
+
}, [projectRoot, manifest, devProperties, force, production, activate, signingPassword]);
|
|
101
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
102
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
103
|
+
React.createElement(StatusIndicator, { status: state.status === 'deploying' ? 'running' : state.status, label: state.status === 'deploying' ? 'Deploying' : state.status === 'success' ? 'Deployed' : 'Failed', message: state.message })),
|
|
104
|
+
state.status === 'success' && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
105
|
+
React.createElement(Text, { color: "green" },
|
|
106
|
+
production ? 'Production deployment' : 'Dev deployment',
|
|
107
|
+
" complete"),
|
|
108
|
+
state.executableId && (React.createElement(Text, { dimColor: true },
|
|
109
|
+
"Executable ID: ",
|
|
110
|
+
state.executableId)),
|
|
111
|
+
force && React.createElement(Text, { dimColor: true }, "(Force mode - overwrote existing)"),
|
|
112
|
+
activate && production && React.createElement(Text, { dimColor: true }, "(Activated)"))),
|
|
113
|
+
state.status === 'error' && state.error && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
114
|
+
React.createElement(Text, { color: "red" }, state.error))),
|
|
115
|
+
state.status !== 'deploying' && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
116
|
+
state.status === 'error' && onRetryCredentials && (React.createElement(Text, { dimColor: true }, "Press r to retry with new credentials")),
|
|
117
|
+
onBack && (React.createElement(Text, { dimColor: true }, "Press q or Esc to return to menu"))))));
|
|
118
|
+
}
|
|
119
|
+
export const deployCommand = {
|
|
120
|
+
name: 'deploy',
|
|
121
|
+
description: 'Deploy the application',
|
|
122
|
+
requiresProject: true,
|
|
123
|
+
flags: {
|
|
124
|
+
force: {
|
|
125
|
+
type: 'boolean',
|
|
126
|
+
description: 'Force deployment (overwrite existing)',
|
|
127
|
+
alias: 'f',
|
|
128
|
+
default: false,
|
|
129
|
+
},
|
|
130
|
+
production: {
|
|
131
|
+
type: 'boolean',
|
|
132
|
+
description: 'Deploy to production (requires signed app)',
|
|
133
|
+
alias: 'p',
|
|
134
|
+
default: false,
|
|
135
|
+
},
|
|
136
|
+
activate: {
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
description: 'Activate the app after production deployment',
|
|
139
|
+
alias: 'a',
|
|
140
|
+
default: false,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
async execute({ project, flags }) {
|
|
144
|
+
// Check if dev properties are configured
|
|
145
|
+
if (!project.hasDevProperties || !project.devProperties) {
|
|
146
|
+
console.log('\n\x1b[33mDeployment credentials not configured.\x1b[0m');
|
|
147
|
+
console.log('Create a .dev_properties.json file with domain, siteName, addonName, username, and password.\n');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const production = Boolean(flags['production']);
|
|
151
|
+
const force = Boolean(flags['force']);
|
|
152
|
+
const activate = Boolean(flags['activate']);
|
|
153
|
+
// For production, we need the signed zip, which requires signing credentials
|
|
154
|
+
let signingPassword;
|
|
155
|
+
if (production && project.hasSigningProperties && project.devProperties.signingUsername) {
|
|
156
|
+
// We already have a signed zip, no need to prompt for password here
|
|
157
|
+
// The sign command should have been run separately
|
|
158
|
+
}
|
|
159
|
+
const { waitUntilExit } = render(React.createElement(DeployScreen, { projectRoot: project.root, manifest: project.manifest, devProperties: project.devProperties, force: force, production: production, activate: activate, signingPassword: signingPassword }));
|
|
160
|
+
await waitUntilExit();
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Command } from './types.js';
|
|
3
|
+
import type { SitevisionManifest, DevProperties, SigningCredentials } from '../types/index.js';
|
|
4
|
+
interface DevScreenProps {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
manifest: SitevisionManifest;
|
|
7
|
+
devProperties: DevProperties;
|
|
8
|
+
signed: boolean;
|
|
9
|
+
signingCredentials?: SigningCredentials;
|
|
10
|
+
onBack?: () => void;
|
|
11
|
+
onRetryCredentials?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function DevScreen({ projectRoot, manifest, devProperties, signed, signingCredentials, onBack, onRetryCredentials, }: DevScreenProps): React.JSX.Element;
|
|
14
|
+
export declare const devCommand: Command;
|
|
15
|
+
export {};
|