sitevision-cli 1.0.0-beta.2 → 1.0.0-beta.4

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 CHANGED
@@ -2,5 +2,5 @@ import { type ProjectInfo } from './utils/project-detection.js';
2
2
  type Props = {
3
3
  project: ProjectInfo;
4
4
  };
5
- export default function App({ project }: Props): import("react").JSX.Element | null;
5
+ export default function App({ project: initialProject }: Props): import("react").JSX.Element | null;
6
6
  export {};
package/dist/app.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo, useState } from 'react';
2
+ import { useCallback, useMemo, useState } from 'react';
3
+ import { detectProject } from './utils/project-detection.js';
3
4
  import { MainMenu } from './components/MainMenu.js';
4
5
  import { InfoScreen } from './components/InfoScreen.js';
5
6
  import { SetupFlow } from './components/SetupFlow.js';
@@ -12,7 +13,22 @@ import { DeployScreen } from './commands/deploy.js';
12
13
  import { SignScreen } from './commands/sign.js';
13
14
  import { SigningPropertiesForm } from './components/SigningPropertiesForm.js';
14
15
  import { getSigningPassword, setDeployPassword as saveDeployPassword, setSigningPassword as saveSigningPassword, } from './utils/keychain.js';
15
- export default function App({ project }) {
16
+ export default function App({ project: initialProject }) {
17
+ // The project is loaded once at startup, but setup flows write new values to
18
+ // disk and the OS keychain. Hold it in state so we can re-detect after setup
19
+ // and pick up those changes (e.g. saved passwords) without restarting the CLI.
20
+ const [project, setProject] = useState(initialProject);
21
+ const reloadProject = useCallback(() => {
22
+ try {
23
+ const refreshed = detectProject(initialProject.root);
24
+ if (refreshed)
25
+ setProject(refreshed);
26
+ }
27
+ catch {
28
+ // Re-detection failed (e.g. manifest became unparseable mid-session) —
29
+ // keep the existing in-memory project rather than crashing.
30
+ }
31
+ }, [initialProject.root]);
16
32
  const [state, setState] = useState('setup');
17
33
  const [currentCommand, setCurrentCommand] = useState('');
18
34
  const [signingPassword, setSigningPassword] = useState('');
@@ -185,7 +201,7 @@ export default function App({ project }) {
185
201
  }
186
202
  };
187
203
  if (state === 'setup') {
188
- return _jsx(SetupFlow, { project: project, onComplete: () => setState('menu') });
204
+ return (_jsx(SetupFlow, { project: project, onReload: reloadProject, onComplete: () => setState('menu') }));
189
205
  }
190
206
  if (state === 'menu') {
191
207
  return _jsx(MainMenu, { project: project, onSelect: handleCommandSelect });
@@ -260,9 +276,9 @@ export default function App({ project }) {
260
276
  }
261
277
  if (state === 'setup-signing') {
262
278
  return (_jsx(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
263
- // We can't easily update project info here without full reload,
264
- // but since we are just returning to menu, it's fine.
265
- // The user might need to restart CLI or we implement a reload mechanism.
279
+ // Re-detect so the newly written signing credentials are reflected
280
+ // in memory (hasSigningProperties, keychain password) without a restart.
281
+ reloadProject();
266
282
  setState('menu');
267
283
  }, onCancel: () => setState('menu') }));
268
284
  }
@@ -11,7 +11,7 @@ import { signApp, deployApp } from '../utils/sitevision-api.js';
11
11
  import { setDeployPassword } from '../utils/keychain.js';
12
12
  import { resolveSigningPassword } from '../utils/signing-password.js';
13
13
  import { copyStaticToBuild, createBuildZip, cleanBuild } from '../utils/zip.js';
14
- import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, } from '../utils/project-detection.js';
14
+ import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, localizedText, } from '../utils/project-detection.js';
15
15
  export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy = true, signingCredentials, onBack, onRetryCredentials, }) {
16
16
  const { exit } = useApp();
17
17
  const [state, setState] = React.useState({
@@ -353,7 +353,7 @@ export function DevScreen({ projectRoot, manifest, devProperties, signed, deploy
353
353
  };
354
354
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusType(), label: getStatusLabel(), message: state.message }) }), state.warning && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", state.warning] }) })), state.buildCount > 0 && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Builds: ", state.buildCount, state.lastBuildTime
355
355
  ? ` | Last build: ${state.lastBuildTime}ms`
356
- : '', signed ? ' | Signed mode' : ''] }) })), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [manifest.name, " v", manifest.version] }) }), deploy && devProperties && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Target: ", devProperties.domain, "/", devProperties.siteName, "/", devProperties.addonName] }) })), state.status === 'error' && state.error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "red", children: state.error }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.status === 'error' && onRetryCredentials && (_jsx(Text, { dimColor: true, children: "Press r to retry with new credentials" })), onBack ? (_jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu (Ctrl+C to stop process)" })) : (_jsx(Text, { dimColor: true, children: "Press Ctrl+C to stop" }))] })] }));
356
+ : '', signed ? ' | Signed mode' : ''] }) })), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [localizedText(manifest.name), " v", manifest.version] }) }), deploy && devProperties && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Target: ", devProperties.domain, "/", devProperties.siteName, "/", devProperties.addonName] }) })), state.status === 'error' && state.error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "red", children: state.error }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.status === 'error' && onRetryCredentials && (_jsx(Text, { dimColor: true, children: "Press r to retry with new credentials" })), onBack ? (_jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu (Ctrl+C to stop process)" })) : (_jsx(Text, { dimColor: true, children: "Press Ctrl+C to stop" }))] })] }));
357
357
  }
358
358
  export const devCommand = {
359
359
  name: 'dev',
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { render } from 'ink';
3
3
  import { Box, Text } from 'ink';
4
- import { getAppType } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText } from '../utils/project-detection.js';
5
5
  function InfoScreen({ project }) {
6
6
  const appType = getAppType(project.manifest);
7
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 No dev properties found. Run", ' ', _jsx(Text, { bold: true, children: "setup-dev-properties" }), " to configure."] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
7
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 No dev properties found. Run", ' ', _jsx(Text, { bold: true, children: "setup-dev-properties" }), " to configure."] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
8
8
  }
9
9
  export const infoCommand = {
10
10
  name: 'info',
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput } from 'ink';
3
- import { getAppType } from '../utils/project-detection.js';
3
+ import { getAppType, localizedText, } from '../utils/project-detection.js';
4
4
  import { checkSitevisionScriptsCompatibility } from '../utils/sitevision-scripts-runner.js';
5
5
  export function InfoScreen({ project, onBack }) {
6
6
  const appType = getAppType(project.manifest);
@@ -10,5 +10,5 @@ export function InfoScreen({ project, onBack }) {
10
10
  onBack();
11
11
  }
12
12
  });
13
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Build Tooling" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "sitevision-scripts: " }), _jsx(Text, { children: scriptsCompat.installed ?? 'not installed' }), _jsxs(Text, { dimColor: true, children: [" (supported ", scriptsCompat.supportedRange, ")"] })] }), scriptsCompat.warning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", scriptsCompat.warning] }) }))] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Run setup-dev-properties to configure." }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: "Press Enter or ESC to return to menu" }) })] }));
13
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Build Tooling" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "sitevision-scripts: " }), _jsx(Text, { children: scriptsCompat.installed ?? 'not installed' }), _jsxs(Text, { dimColor: true, children: [" (supported ", scriptsCompat.supportedRange, ")"] })] }), scriptsCompat.warning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", scriptsCompat.warning] }) }))] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Run setup-dev-properties to configure." }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: "Press Enter or ESC to return to menu" }) })] }));
14
14
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
- import { getAppType } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText } from '../utils/project-detection.js';
5
5
  export function MainMenu({ project, onSelect }) {
6
6
  const appType = getAppType(project.manifest);
7
7
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -79,5 +79,5 @@ export function MainMenu({ project, onSelect }) {
79
79
  onSelect(selectedItem.value);
80
80
  }
81
81
  });
82
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), project.hasSigningProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Signing Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Signing User: " }), _jsx(Text, { children: project.devProperties.signingUsername })] }), project.devProperties.certificateName && (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Certificate: " }), _jsx(Text, { children: project.devProperties.certificateName })] }))] })] })), !project.hasDevProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Some commands may not work." }) })), project.hasDevProperties && !project.hasSigningProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No signing credentials configured. Run svc setup-signing to configure." }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a command:" }) }), _jsx(Box, { flexDirection: "column", children: items.map((item, index) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: index === selectedIndex ? 'cyan' : undefined, bold: index === selectedIndex, children: [index === selectedIndex ? '▶ ' : ' ', item.label, item.description && _jsxs(Text, { dimColor: true, children: [" - ", item.description] })] }) }, item.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 arrows to navigate, Enter to select" }) })] }));
82
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), project.hasSigningProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Signing Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Signing User: " }), _jsx(Text, { children: project.devProperties.signingUsername })] }), project.devProperties.certificateName && (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Certificate: " }), _jsx(Text, { children: project.devProperties.certificateName })] }))] })] })), !project.hasDevProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties found. Some commands may not work." }) })), project.hasDevProperties && !project.hasSigningProperties && (_jsx(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "yellow", children: _jsx(Text, { color: "yellow", children: "\u26A0 No signing credentials configured. Run svc setup-signing to configure." }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a command:" }) }), _jsx(Box, { flexDirection: "column", children: items.map((item, index) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: index === selectedIndex ? 'cyan' : undefined, bold: index === selectedIndex, children: [index === selectedIndex ? '▶ ' : ' ', item.label, item.description && _jsxs(Text, { dimColor: true, children: [" - ", item.description] })] }) }, item.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 arrows to navigate, Enter to select" }) })] }));
83
83
  }
@@ -1,7 +1,8 @@
1
1
  import { type ProjectInfo } from '../utils/project-detection.js';
2
2
  interface Props {
3
3
  project: ProjectInfo;
4
+ onReload: () => void;
4
5
  onComplete: () => void;
5
6
  }
6
- export declare function SetupFlow({ project, onComplete }: Props): import("react").JSX.Element | null;
7
+ export declare function SetupFlow({ project, onReload, onComplete }: Props): import("react").JSX.Element | null;
7
8
  export {};
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
- import { getAppType, migrateLegacyPassword, } from '../utils/project-detection.js';
4
+ import { getAppType, localizedText, migrateLegacyPassword, } from '../utils/project-detection.js';
5
5
  import { ProcessRunner } from '../utils/process-runner.js';
6
6
  import { ProcessOutputComponent } from './ProcessOutput.js';
7
7
  import { StatusIndicator } from './StatusIndicator.js';
8
8
  import { DevPropertiesForm } from './DevPropertiesForm.js';
9
9
  import { SigningPropertiesForm } from './SigningPropertiesForm.js';
10
- export function SetupFlow({ project, onComplete }) {
10
+ export function SetupFlow({ project, onReload, onComplete }) {
11
11
  const [step, setStep] = useState('check-node-modules');
12
12
  const [runner, setRunner] = useState(null);
13
13
  const [commandStatus, setCommandStatus] = useState('running');
@@ -84,6 +84,9 @@ export function SetupFlow({ project, onComplete }) {
84
84
  else if (step === 'confirm-password-migration') {
85
85
  if (input === 'y' || input === 'Y') {
86
86
  migrateLegacyPassword(project);
87
+ // The file was rewritten (plaintext stripped, password moved to
88
+ // keychain) — re-detect so hasLegacyPassword/password reflect that.
89
+ onReload();
87
90
  setStep('check-signing-properties');
88
91
  }
89
92
  else if (input === 'n' || input === 'N') {
@@ -102,17 +105,19 @@ export function SetupFlow({ project, onComplete }) {
102
105
  // Setup Dev Properties Form
103
106
  if (step === 'setup-dev-properties') {
104
107
  return (_jsx(DevPropertiesForm, { projectRoot: project.root, initialProperties: project.devProperties, packageJson: project.packageJson, onComplete: () => {
105
- // Manually update project state locally if possible, or just proceed
106
- // Since we can't easily update 'project' prop from here without reloading,
107
- // we just move to next step. The file is written.
108
- project.hasDevProperties = true; // Optimization/Hack to pass check
108
+ // Re-detect from disk/keychain so devProperties (incl. the keychain
109
+ // password) populate in memory otherwise the rest of this flow and
110
+ // the menu would see stale state until the CLI is restarted.
111
+ onReload();
109
112
  setStep('check-signing-properties');
110
113
  }, onCancel: () => setStep('check-signing-properties') }));
111
114
  }
112
115
  // Setup Signing Properties Form
113
116
  if (step === 'setup-signing-properties') {
114
117
  return (_jsx(SigningPropertiesForm, { projectRoot: project.root, onComplete: () => {
115
- project.hasSigningProperties = true; // Optimization/Hack
118
+ // Re-detect so signing credentials are reflected in memory before
119
+ // the info screen / menu render.
120
+ onReload();
116
121
  setStep('show-info');
117
122
  }, onCancel: () => setStep('show-info') }));
118
123
  }
@@ -138,7 +143,7 @@ export function SetupFlow({ project, onComplete }) {
138
143
  }
139
144
  // Show info
140
145
  if (step === 'show-info') {
141
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: project.manifest.name })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
146
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Sitevision Project Information" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Name: " }), _jsx(Text, { children: localizedText(project.manifest.name) })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ID: " }), _jsx(Text, { children: project.manifest.id })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Version: " }), _jsx(Text, { children: project.manifest.version })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Type: " }), _jsx(Text, { color: "green", children: project.manifest.type }), _jsxs(Text, { dimColor: true, children: [" (", appType, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Bundled: " }), _jsx(Text, { children: project.manifest.bundled ? 'Yes' : 'No' })] })] }), project.hasDevProperties && project.devProperties && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Development Configuration" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Domain: " }), _jsx(Text, { children: project.devProperties.domain })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Site: " }), _jsx(Text, { children: project.devProperties.siteName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Addon: " }), _jsx(Text, { children: project.devProperties.addonName })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Username: " }), _jsx(Text, { children: project.devProperties.username })] }), _jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Use HTTP: " }), _jsx(Text, { children: project.devProperties.useHTTPForDevDeploy ? 'Yes' : 'No' })] })] })] })), !project.hasDevProperties && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "\u26A0 No dev properties configured" }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Project Root: " }), _jsx(Text, { dimColor: true, children: project.root })] })] }));
142
147
  }
143
148
  return null;
144
149
  }
@@ -11,16 +11,22 @@ export type AppType = 'WebApp' | 'Widget' | 'RESTApp';
11
11
  * Simplified app type for internal use
12
12
  */
13
13
  export type SimpleAppType = 'web' | 'widget' | 'rest';
14
+ /**
15
+ * A manifest text field that may be a plain string or a localized object keyed
16
+ * by language code, e.g. `{sv: 'Namn', en: 'Name'}`. Sitevision allows either
17
+ * form for human-facing fields like `name` and `description`.
18
+ */
19
+ export type LocalizedString = string | Record<string, string>;
14
20
  /**
15
21
  * Sitevision app manifest (manifest.json)
16
22
  */
17
23
  export interface SitevisionManifest {
18
24
  id: string;
19
- name: string;
25
+ name: LocalizedString;
20
26
  version: string;
21
27
  type: AppType;
22
28
  bundled?: boolean;
23
- description?: string;
29
+ description?: LocalizedString;
24
30
  author?: string;
25
31
  helpUrl?: string;
26
32
  license?: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * JSONC helpers.
3
+ *
4
+ * Sitevision's own manifest documentation shows manifest.json with line and
5
+ * block comments (e.g. `"name": { // Multilingual-manifest requires SV 10.1`),
6
+ * so real-world manifests copied from the docs contain them. Strict JSON.parse
7
+ * rejects those, so we strip comments before parsing.
8
+ */
9
+ /**
10
+ * Remove line comments (//...) and block comments from a JSON string, leaving
11
+ * everything inside string literals untouched (so values like
12
+ * `"https://example.com"` survive).
13
+ */
14
+ export declare function stripJsonComments(input: string): string;
15
+ /**
16
+ * Parse a JSON string that may contain comments (JSONC). Throws the underlying
17
+ * SyntaxError if the content is invalid even after comments are removed.
18
+ */
19
+ export declare function parseJsonc<T>(input: string): T;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * JSONC helpers.
3
+ *
4
+ * Sitevision's own manifest documentation shows manifest.json with line and
5
+ * block comments (e.g. `"name": { // Multilingual-manifest requires SV 10.1`),
6
+ * so real-world manifests copied from the docs contain them. Strict JSON.parse
7
+ * rejects those, so we strip comments before parsing.
8
+ */
9
+ /**
10
+ * Remove line comments (//...) and block comments from a JSON string, leaving
11
+ * everything inside string literals untouched (so values like
12
+ * `"https://example.com"` survive).
13
+ */
14
+ export function stripJsonComments(input) {
15
+ let result = '';
16
+ let inString = false;
17
+ let inLineComment = false;
18
+ let inBlockComment = false;
19
+ for (let i = 0; i < input.length; i++) {
20
+ const char = input[i];
21
+ const next = input[i + 1];
22
+ if (inLineComment) {
23
+ if (char === '\n') {
24
+ inLineComment = false;
25
+ result += char;
26
+ }
27
+ continue;
28
+ }
29
+ if (inBlockComment) {
30
+ if (char === '*' && next === '/') {
31
+ inBlockComment = false;
32
+ i++;
33
+ }
34
+ continue;
35
+ }
36
+ if (inString) {
37
+ result += char;
38
+ // Copy escaped characters verbatim so an escaped quote (\") does not
39
+ // end the string early.
40
+ if (char === '\\') {
41
+ result += next ?? '';
42
+ i++;
43
+ }
44
+ else if (char === '"') {
45
+ inString = false;
46
+ }
47
+ continue;
48
+ }
49
+ if (char === '"') {
50
+ inString = true;
51
+ result += char;
52
+ continue;
53
+ }
54
+ if (char === '/' && next === '/') {
55
+ inLineComment = true;
56
+ i++;
57
+ continue;
58
+ }
59
+ if (char === '/' && next === '*') {
60
+ inBlockComment = true;
61
+ i++;
62
+ continue;
63
+ }
64
+ result += char;
65
+ }
66
+ return result;
67
+ }
68
+ /**
69
+ * Parse a JSON string that may contain comments (JSONC). Throws the underlying
70
+ * SyntaxError if the content is invalid even after comments are removed.
71
+ */
72
+ export function parseJsonc(input) {
73
+ return JSON.parse(stripJsonComments(input));
74
+ }
@@ -1,5 +1,15 @@
1
- import type { SitevisionManifest, DevProperties, ProjectInfo, ProjectPaths, SimpleAppType, ApiEndpoints } from '../types/index.js';
1
+ import type { SitevisionManifest, DevProperties, ProjectInfo, ProjectPaths, SimpleAppType, ApiEndpoints, LocalizedString } from '../types/index.js';
2
2
  export type { SitevisionManifest, DevProperties, ProjectInfo, } from '../types/index.js';
3
+ /**
4
+ * Resolve a manifest text field that may be a plain string or a localized
5
+ * object (e.g. `{sv: 'Namn', en: 'Name'}`) to a single display string.
6
+ *
7
+ * Preference order: Swedish, then English, then any available language. Returns
8
+ * an empty string for missing/empty values. This guards the UI from rendering a
9
+ * raw object as a React child, which Sitevision's localized manifests would
10
+ * otherwise trigger.
11
+ */
12
+ export declare function localizedText(value: LocalizedString | undefined, preferred?: string): string;
3
13
  /**
4
14
  * Get standard project paths for a given root directory
5
15
  */
@@ -51,6 +61,14 @@ export declare function buildAddonEndpointUrl(domain: string, siteName: string,
51
61
  * Build the import endpoint URL
52
62
  */
53
63
  export declare function buildImportEndpointUrl(domain: string, siteName: string, addonName: string, appType: SimpleAppType, useHTTP?: boolean): string;
64
+ /**
65
+ * Thrown when a manifest.json is present but cannot be parsed. Kept distinct from
66
+ * a plain "no project here" (null) so the CLI can tell the user their manifest is
67
+ * malformed instead of the misleading "Not a Sitevision project".
68
+ */
69
+ export declare class ManifestParseError extends Error {
70
+ constructor(manifestPath: string, cause: unknown);
71
+ }
54
72
  /**
55
73
  * Detect if the current directory is a Sitevision project
56
74
  */
@@ -1,6 +1,26 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getDeployPassword, setDeployPassword } from './keychain.js';
4
+ import { parseJsonc } from './jsonc.js';
5
+ // =============================================================================
6
+ // LOCALIZED TEXT
7
+ // =============================================================================
8
+ /**
9
+ * Resolve a manifest text field that may be a plain string or a localized
10
+ * object (e.g. `{sv: 'Namn', en: 'Name'}`) to a single display string.
11
+ *
12
+ * Preference order: Swedish, then English, then any available language. Returns
13
+ * an empty string for missing/empty values. This guards the UI from rendering a
14
+ * raw object as a React child, which Sitevision's localized manifests would
15
+ * otherwise trigger.
16
+ */
17
+ export function localizedText(value, preferred = 'sv') {
18
+ if (!value)
19
+ return '';
20
+ if (typeof value === 'string')
21
+ return value;
22
+ return value[preferred] ?? value['en'] ?? Object.values(value)[0] ?? '';
23
+ }
4
24
  // =============================================================================
5
25
  // PATH UTILITIES
6
26
  // =============================================================================
@@ -135,6 +155,18 @@ export function buildImportEndpointUrl(domain, siteName, addonName, appType, use
135
155
  // =============================================================================
136
156
  // PROJECT DETECTION
137
157
  // =============================================================================
158
+ /**
159
+ * Thrown when a manifest.json is present but cannot be parsed. Kept distinct from
160
+ * a plain "no project here" (null) so the CLI can tell the user their manifest is
161
+ * malformed instead of the misleading "Not a Sitevision project".
162
+ */
163
+ export class ManifestParseError extends Error {
164
+ constructor(manifestPath, cause) {
165
+ const reason = cause instanceof Error ? cause.message : String(cause);
166
+ super(`${manifestPath} is not valid JSON: ${reason}`);
167
+ this.name = 'ManifestParseError';
168
+ }
169
+ }
138
170
  /**
139
171
  * Detect if the current directory is a Sitevision project
140
172
  */
@@ -151,7 +183,16 @@ export function detectProject(cwd = process.cwd()) {
151
183
  for (const p of manifestPaths) {
152
184
  if (fs.existsSync(p)) {
153
185
  manifestPath = p;
154
- manifest = JSON.parse(fs.readFileSync(p, 'utf-8'));
186
+ // Manifests may contain comments (Sitevision's own docs show them), so
187
+ // parse as JSONC. A still-unparseable manifest is a real, fixable
188
+ // error — surface it rather than silently reporting "Not a Sitevision
189
+ // project".
190
+ try {
191
+ manifest = parseJsonc(fs.readFileSync(p, 'utf-8'));
192
+ }
193
+ catch (error) {
194
+ throw new ManifestParseError(p, error);
195
+ }
155
196
  break;
156
197
  }
157
198
  }
@@ -219,7 +260,12 @@ export function detectProject(cwd = process.cwd()) {
219
260
  paths,
220
261
  };
221
262
  }
222
- catch {
263
+ catch (error) {
264
+ // A malformed manifest is a real error the user should see; everything else
265
+ // (missing files, unreadable optional config) just means "no project here".
266
+ if (error instanceof ManifestParseError) {
267
+ throw error;
268
+ }
223
269
  return null;
224
270
  }
225
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"