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 +1 -1
- package/dist/app.js +22 -6
- package/dist/commands/dev.js +2 -2
- package/dist/commands/info.js +2 -2
- package/dist/components/InfoScreen.js +2 -2
- package/dist/components/MainMenu.js +2 -2
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +13 -8
- package/dist/types/index.d.ts +8 -2
- package/dist/utils/jsonc.d.ts +19 -0
- package/dist/utils/jsonc.js +74 -0
- package/dist/utils/project-detection.d.ts +19 -1
- package/dist/utils/project-detection.js +48 -2
- package/package.json +1 -1
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
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
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
|
}
|
package/dist/commands/dev.js
CHANGED
|
@@ -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',
|
package/dist/commands/info.js
CHANGED
|
@@ -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
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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:
|
|
25
|
+
name: LocalizedString;
|
|
20
26
|
version: string;
|
|
21
27
|
type: AppType;
|
|
22
28
|
bundled?: boolean;
|
|
23
|
-
description?:
|
|
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
|
-
|
|
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
|
}
|