sitevision-cli 1.0.0-beta.3 → 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
  }
@@ -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 {};
@@ -7,7 +7,7 @@ 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
  }
@@ -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,6 +1,7 @@
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';
4
5
  // =============================================================================
5
6
  // LOCALIZED TEXT
6
7
  // =============================================================================
@@ -182,12 +183,12 @@ export function detectProject(cwd = process.cwd()) {
182
183
  for (const p of manifestPaths) {
183
184
  if (fs.existsSync(p)) {
184
185
  manifestPath = p;
185
- // A present-but-unparseable manifest is a real, fixable error (a stray
186
- // comment, a trailing comma, …). Surface it rather than silently
187
- // reporting "Not a Sitevision project". JSON has no comments strip
188
- // any `//` annotations from manifest.json.
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".
189
190
  try {
190
- manifest = JSON.parse(fs.readFileSync(p, 'utf-8'));
191
+ manifest = parseJsonc(fs.readFileSync(p, 'utf-8'));
191
192
  }
192
193
  catch (error) {
193
194
  throw new ManifestParseError(p, error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"