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
package/dist/app.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { type ProjectInfo } from './utils/project-detection.js';
3
+ type Props = {
4
+ project: ProjectInfo;
5
+ };
6
+ export default function App({ project }: Props): React.JSX.Element | null;
7
+ export {};
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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 {};