sitevision-cli 1.0.0-beta.31 → 1.0.0-beta.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/dist/cli.js +73 -34
  4. package/dist/commands/TaskScreen.js +32 -0
  5. package/dist/commands/build.js +6 -185
  6. package/dist/commands/deploy.js +85 -238
  7. package/dist/commands/dev.js +34 -37
  8. package/dist/commands/info.js +1 -1
  9. package/dist/commands/setup-signing.js +43 -28
  10. package/dist/commands/sign.js +9 -86
  11. package/dist/commands/use-finish.js +17 -0
  12. package/dist/commands/watch.js +0 -8
  13. package/dist/components/AuthLoginScreen.js +27 -12
  14. package/dist/scaffold/inquirer-stub.js +119 -0
  15. package/dist/scaffold/register.js +15 -0
  16. package/dist/shell/ConfigForm.js +174 -29
  17. package/dist/shell/Frame.js +4 -1
  18. package/dist/shell/Help.js +2 -1
  19. package/dist/shell/Settings.js +59 -6
  20. package/dist/shell/Shell.js +361 -43
  21. package/dist/shell/Tabs.js +8 -2
  22. package/dist/shell/actions.js +0 -0
  23. package/dist/utils/config.js +46 -3
  24. package/dist/utils/create-app.js +226 -0
  25. package/dist/utils/i18n.js +68 -13
  26. package/dist/utils/jsonc.js +2 -1
  27. package/dist/utils/keychain.js +44 -4
  28. package/dist/utils/oauth2-auth.js +35 -7
  29. package/dist/utils/password-prompt.js +23 -3
  30. package/dist/utils/process-runner.js +8 -3
  31. package/dist/utils/project-detection.js +172 -44
  32. package/dist/utils/session-cookie-auth.js +5 -4
  33. package/dist/utils/sitevision-api.js +21 -14
  34. package/dist/utils/sitevision-scripts-runner.js +32 -15
  35. package/dist/utils/tasks.js +132 -66
  36. package/dist/utils/version-check.js +3 -3
  37. package/dist/utils/webpack-runner.js +6 -5
  38. package/dist/utils/workspace.js +41 -3
  39. package/dist/utils/zip.js +18 -19
  40. package/package.json +24 -3
  41. package/readme.md +11 -4
  42. package/dist/app.d.ts +0 -6
  43. package/dist/app.js +0 -321
  44. package/dist/cli.d.ts +0 -2
  45. package/dist/commands/build.d.ts +0 -12
  46. package/dist/commands/deploy.d.ts +0 -17
  47. package/dist/commands/dev.d.ts +0 -14
  48. package/dist/commands/index.d.ts +0 -4
  49. package/dist/commands/info.d.ts +0 -2
  50. package/dist/commands/setup-signing.d.ts +0 -2
  51. package/dist/commands/sign.d.ts +0 -14
  52. package/dist/commands/types.d.ts +0 -18
  53. package/dist/commands/watch.d.ts +0 -2
  54. package/dist/components/AnimatedLogo.d.ts +0 -10
  55. package/dist/components/AuthLoginScreen.d.ts +0 -21
  56. package/dist/components/DevPropertiesForm.d.ts +0 -11
  57. package/dist/components/DevPropertiesForm.js +0 -246
  58. package/dist/components/InfoScreen.d.ts +0 -7
  59. package/dist/components/InfoScreen.js +0 -14
  60. package/dist/components/KeychainPasswordChoice.d.ts +0 -8
  61. package/dist/components/KeychainPasswordChoice.js +0 -30
  62. package/dist/components/MainMenu.d.ts +0 -7
  63. package/dist/components/MainMenu.js +0 -88
  64. package/dist/components/PasswordInput.d.ts +0 -10
  65. package/dist/components/ProcessOutput.d.ts +0 -6
  66. package/dist/components/ProcessOutput.js +0 -30
  67. package/dist/components/SetupFlow.d.ts +0 -8
  68. package/dist/components/SetupFlow.js +0 -233
  69. package/dist/components/SigningPropertiesForm.d.ts +0 -7
  70. package/dist/components/SigningPropertiesForm.js +0 -38
  71. package/dist/components/StatusIndicator.d.ts +0 -8
  72. package/dist/components/TextInput.d.ts +0 -10
  73. package/dist/components/TextInput.js +0 -29
  74. package/dist/components/WelcomeScreen.d.ts +0 -14
  75. package/dist/shell/AddonPicker.d.ts +0 -14
  76. package/dist/shell/Changelog.d.ts +0 -10
  77. package/dist/shell/CommandPalette.d.ts +0 -12
  78. package/dist/shell/ConfigForm.d.ts +0 -36
  79. package/dist/shell/Frame.d.ts +0 -59
  80. package/dist/shell/Help.d.ts +0 -9
  81. package/dist/shell/Popover.d.ts +0 -12
  82. package/dist/shell/Settings.d.ts +0 -6
  83. package/dist/shell/Shell.d.ts +0 -10
  84. package/dist/shell/Tabs.d.ts +0 -36
  85. package/dist/shell/actions.d.ts +0 -48
  86. package/dist/types/index.d.ts +0 -295
  87. package/dist/utils/branding.d.ts +0 -28
  88. package/dist/utils/config.d.ts +0 -27
  89. package/dist/utils/environments.d.ts +0 -20
  90. package/dist/utils/i18n.d.ts +0 -12
  91. package/dist/utils/jsonc.d.ts +0 -19
  92. package/dist/utils/keychain.d.ts +0 -15
  93. package/dist/utils/oauth2-auth.d.ts +0 -64
  94. package/dist/utils/password-prompt.d.ts +0 -14
  95. package/dist/utils/process-runner.d.ts +0 -31
  96. package/dist/utils/project-detection.d.ts +0 -202
  97. package/dist/utils/session-cookie-auth.d.ts +0 -35
  98. package/dist/utils/signing-password.d.ts +0 -12
  99. package/dist/utils/signing-step.d.ts +0 -15
  100. package/dist/utils/signing-step.js +0 -16
  101. package/dist/utils/sitevision-api.d.ts +0 -155
  102. package/dist/utils/sitevision-scripts-runner.d.ts +0 -84
  103. package/dist/utils/tasks.d.ts +0 -50
  104. package/dist/utils/version-check.d.ts +0 -9
  105. package/dist/utils/webpack-runner.d.ts +0 -76
  106. package/dist/utils/workspace.d.ts +0 -17
  107. package/dist/utils/zip.d.ts +0 -71
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-beta.33
4
+
5
+ - **Breaking:** `--token` and `--cookie` are removed, since credentials on the
6
+ command line end up in shell history. Use `SITEVISION_ACCESS_TOKEN` or
7
+ `SITEVISION_SESSION_COOKIE` instead.
8
+ - **Breaking:** an unknown flag is now an error (exit code 2), so a misspelt
9
+ `--production` never turns into a dev deploy.
10
+ - `svc build`, `svc sign` and `svc deploy` work without a terminal. They print
11
+ their log line by line and exit on their own with a proper exit code: 0 on
12
+ success, 1 on failure, 130 when a prompt is cancelled. `svc dev` and
13
+ `svc watch` no longer crash when stdin is a pipe.
14
+ - New flags: `--no-zip` for `build`, and the short forms `-s`, `-f`, `-p` and
15
+ `-a`.
16
+ - Global settings in `~/.config/sitevision-cli/config.json`: a default signing
17
+ user and certificate for every project that does not set its own, and
18
+ switches for the update check and the addon name warning. Edit them with `,`
19
+ in the shell, which now shows a description of each setting and opens from
20
+ anywhere, or run `svc setup-signing --global`. A file that does not parse is
21
+ reported and left alone.
22
+ - The Config tab marks the addon name with `≠ manifest` when it matches none
23
+ of the manifest's names. Nothing is changed for you; the two are different
24
+ things and `svc` only points the difference out.
25
+ - The manifest's name and description always have a Swedish row in the Config
26
+ tab. Filling it in on a plain name keeps the old text as the English one.
27
+ - In a workspace, an environment's addon name is stored per app. Setting it
28
+ for one app no longer leaks into the others.
29
+ - `svc` names the missing setting when the deploy config is incomplete,
30
+ instead of posting to "undefined".
31
+ - A production deploy with `--activate` fails if the activation did not
32
+ happen, and the confirmation names the addon it deploys to.
33
+ - OAuth2 requires https and a token endpoint on the site's own domain. A
34
+ refresh token is only thrown away when the site rejects it, not on a network
35
+ error. An expired session cookie is removed so the next deploy asks for a
36
+ new login.
37
+ - A plaintext password in `.dev_properties.json` is moved to the keychain when
38
+ the file is saved, and `svc` says so when the keychain cannot store a secret.
39
+ `SVC_NO_KEYCHAIN=1` turns the keychain off for CI.
40
+ - `svc dev` with a project's own webpack config no longer deploys a half-built
41
+ zip when several files change at once.
42
+ - Builds work when dependencies are hoisted to the workspace root, a
43
+ `manifest.json` in the project root is picked up, file names with å, ä and ö
44
+ survive in the zip, and `SITEVISION_APP_ID_PREFIX`/`_SUFFIX` apply to build,
45
+ sign and deploy alike.
46
+ - A broken app in a workspace is skipped with a message instead of stopping
47
+ the shell. The shell asks before quitting during app creation, the PROD
48
+ badge survives a narrow terminal, Esc cancels a confirm, and Ctrl-modified
49
+ keys never fire a shortcut.
50
+ - The update check is skipped without a terminal and when `CI` is set.
51
+ - Updated user guides in English and Swedish.
52
+
53
+ ## 1.0.0-beta.32
54
+
55
+ - Sitevision CLI can now create new apps. **New app** in the command palette
56
+ runs Sitevision's own `create-sitevision-app` as a managed experience and
57
+ guides you through the setup: pick a name and a folder, then answer the
58
+ tool's questions right in `svc`. What your workspace already knows (domain,
59
+ site name, username) is filled in for you, and passwords never end up in a
60
+ file.
61
+ - When the app is created, `svc` fills in its manifest (the name, plus author
62
+ and help URL from the workspace's `package.json`) and offers to create the
63
+ addon on the site, so the app is ready for its first deploy.
64
+ - If the tool cannot be run this way, `svc` hands it the terminal and picks up
65
+ again when it is done.
66
+ - The Config tab has a MANIFEST section for editing `manifest.json`: id,
67
+ version, name, description, author and help URL, one row per language for
68
+ localized fields. Comments and formatting in the file are kept.
69
+ - `y` (sync `package.json`) now also mirrors the manifest's `version`,
70
+ `description` and `author` into the app's `package.json`. An app whose
71
+ `package.json` lacks them shows as out of sync until `y` is pressed.
72
+
3
73
  ## 1.0.0-beta.31
4
74
 
5
75
  - The help panel (`?`) now starts with a short guide to the pane you are in:
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rasmus Söderström
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import { requireProject, detectProject, migrateLegacyPassword, } from './utils/p
13
13
  import { discoverApps } from './utils/workspace.js';
14
14
  import { promptYesNo } from './utils/password-prompt.js';
15
15
  import { checkForUpdate } from './utils/version-check.js';
16
- import { isFirstRun, markFirstRunComplete, getLastSeenVersion, setLastSeenVersion, getSettings, } from './utils/config.js';
16
+ import { isFirstRun, markFirstRunComplete, getLastSeenVersion, setLastSeenVersion, getSettings, configProblem, settingsFile, } from './utils/config.js';
17
17
  import { setLanguage } from './utils/i18n.js';
18
18
  import { WelcomeScreen } from './components/WelcomeScreen.js';
19
19
  import { AnimatedLogo } from './components/AnimatedLogo.js';
@@ -31,11 +31,21 @@ const cli = meow(`
31
31
  sign Sign the app for production deployment
32
32
  deploy Deploy the application
33
33
  info Show project information
34
+ setup-signing Store the signing username and certificate
34
35
 
35
36
  Options
36
- --minimal Shell: compact layout for small terminals
37
- --help Show this help message
38
- --version Show version number
37
+ --signed, -s dev/watch: sign after each build
38
+ --force, -f deploy: overwrite the existing addon version
39
+ --production, -p deploy: upload the signed zip to production
40
+ --activate, -a deploy: activate after a production deploy
41
+ --no-zip build: skip the zip archive
42
+ --global setup-signing: save for every project on this machine
43
+ --minimal Shell: compact layout for small terminals
44
+ --help Show this help message
45
+ --version Show version number
46
+
47
+ Credentials for CI come from the environment: SITEVISION_DEPLOY_PASSWORD,
48
+ SITEVISION_ACCESS_TOKEN or SITEVISION_SESSION_COOKIE.
39
49
 
40
50
  Examples
41
51
  $ svc # Shell: run inside an app, or at the repo root
@@ -50,9 +60,20 @@ const cli = meow(`
50
60
  $ svc info
51
61
  `, {
52
62
  importMeta: import.meta,
63
+ allowUnknownFlags: false,
53
64
  flags: {
54
65
  signed: {
55
66
  type: 'boolean',
67
+ shortFlag: 's',
68
+ default: false,
69
+ },
70
+ zip: {
71
+ type: 'boolean',
72
+ default: true,
73
+ },
74
+ activate: {
75
+ type: 'boolean',
76
+ shortFlag: 'a',
56
77
  default: false,
57
78
  },
58
79
  force: {
@@ -65,16 +86,14 @@ const cli = meow(`
65
86
  shortFlag: 'p',
66
87
  default: false,
67
88
  },
68
- token: {
69
- type: 'string',
70
- },
71
- cookie: {
72
- type: 'string',
73
- },
74
89
  minimal: {
75
90
  type: 'boolean',
76
91
  default: false,
77
92
  },
93
+ global: {
94
+ type: 'boolean',
95
+ default: false,
96
+ },
78
97
  },
79
98
  });
80
99
  const [commandName, ...args] = cli.input;
@@ -145,10 +164,19 @@ async function playIntro(art) {
145
164
  // Run the full-screen shell on the alternate screen buffer so the scrollback
146
165
  // is untouched, and restore it on exit. The animated wordmark plays first,
147
166
  // inside the same buffer, when the terminal is wide enough for it.
148
- async function runShell(apps, workspaceRoot, updatedFrom) {
167
+ async function runShell(apps, workspaceRoot, updatedFrom, skipped = []) {
149
168
  process.stdout.write('\x1b[?1049h\x1b[H');
150
169
  // Also leave the alternate screen when a signal exits past the finally.
151
170
  process.once('exit', () => process.stdout.write('\x1b[?1049l'));
171
+ // A crash outside React would print into the alternate screen and vanish
172
+ // with it: leave it first, then report.
173
+ const crash = (error) => {
174
+ process.stdout.write('\x1b[?1049l');
175
+ console.error('svc crashed:', error);
176
+ shutdown(1);
177
+ };
178
+ process.once('uncaughtException', crash);
179
+ process.once('unhandledRejection', crash);
152
180
  try {
153
181
  const art = process.stdin.isTTY && settings.introAnimation && !cli.flags.minimal
154
182
  ? pickIntroArt(process.stdout.columns ?? 0)
@@ -157,8 +185,28 @@ async function runShell(apps, workspaceRoot, updatedFrom) {
157
185
  await playIntro(art);
158
186
  process.stdout.write('\x1b[2J\x1b[H');
159
187
  }
160
- const app = render(_jsx(Shell, { apps: apps, workspaceRoot: workspaceRoot, version: pkg.version, minimal: cli.flags.minimal, updatedFrom: updatedFrom }));
161
- await app.waitUntilExit();
188
+ // A handover unmounts the shell, runs its job on the normal screen with
189
+ // the terminal to itself, and starts the shell again.
190
+ for (;;) {
191
+ let job;
192
+ const app = render(_jsx(Shell, { apps: apps, workspaceRoot: workspaceRoot, version: pkg.version, minimal: cli.flags.minimal, updatedFrom: updatedFrom, skipped: skipped, handover: next => {
193
+ job = next;
194
+ app.unmount();
195
+ } }));
196
+ // eslint-disable-next-line no-await-in-loop
197
+ await app.waitUntilExit();
198
+ if (!job)
199
+ break;
200
+ process.stdout.write('\x1b[?1049l');
201
+ // eslint-disable-next-line no-await-in-loop
202
+ await job();
203
+ if (workspaceRoot) {
204
+ skipped = [];
205
+ apps = discoverApps(workspaceRoot, skipped);
206
+ }
207
+ updatedFrom = undefined;
208
+ process.stdout.write('\x1b[?1049h\x1b[2J\x1b[H');
209
+ }
162
210
  }
163
211
  finally {
164
212
  process.stdout.write('\x1b[?1049l');
@@ -191,8 +239,13 @@ async function main() {
191
239
  }
192
240
  // Record the current version so the banner shows once per upgrade.
193
241
  setLastSeenVersion(pkg.version);
242
+ if (configProblem()) {
243
+ console.log(`\x1b[33m ${settingsFile()} does not parse and is ignored: ${configProblem()}\x1b[0m`);
244
+ }
194
245
  // Check npm for a newer published release.
195
- const latestVersion = await checkForUpdate(pkg.name, pkg.version);
246
+ const latestVersion = settings.updateCheck && process.stdout.isTTY && !process.env['CI']
247
+ ? await checkForUpdate(pkg.name, pkg.version)
248
+ : null;
196
249
  if (latestVersion) {
197
250
  console.log(`\x1b[33m ↑ update available: ${pkg.version} → ${latestVersion} (run: npm i -g ${pkg.name})\x1b[0m`);
198
251
  }
@@ -208,11 +261,13 @@ async function main() {
208
261
  fail(error.message, 'Fix the manifest and try again');
209
262
  }
210
263
  if (!project) {
211
- const apps = discoverApps(process.cwd());
264
+ const skipped = [];
265
+ const apps = discoverApps(process.cwd(), skipped);
212
266
  if (apps.length === 0) {
213
- fail('No Sitevision apps found here.', 'Run svc inside an app directory (manifest.json) or at the root of a repo that contains apps.');
267
+ fail('No Sitevision apps found here.', skipped[0] ??
268
+ 'Run svc inside an app directory (manifest.json) or at the root of a repo that contains apps.');
214
269
  }
215
- await runShell(apps, process.cwd(), isUpdate ? lastSeen : undefined);
270
+ await runShell(apps, process.cwd(), isUpdate ? lastSeen : undefined, skipped);
216
271
  shutdown();
217
272
  return;
218
273
  }
@@ -239,22 +294,6 @@ async function main() {
239
294
  return fail(error.message, "Make sure you're in a Sitevision project directory");
240
295
  }
241
296
  })();
242
- // --token / --cookie override the resolved bearer token / session cookie for
243
- // this run (manual / CI path, alongside SITEVISION_ACCESS_TOKEN and
244
- // SITEVISION_SESSION_COOKIE).
245
- if (cli.flags.token || cli.flags.cookie) {
246
- if (project.devProperties) {
247
- if (cli.flags.token) {
248
- project.devProperties.accessToken = cli.flags.token;
249
- }
250
- if (cli.flags.cookie) {
251
- project.devProperties.sessionCookie = cli.flags.cookie;
252
- }
253
- }
254
- else {
255
- console.log('\x1b[33m--token/--cookie needs a .dev_properties.json (domain, site, addon) to deploy against.\x1b[0m');
256
- }
257
- }
258
297
  // Get the command
259
298
  const command = getCommand(commandName);
260
299
  if (!command) {
@@ -272,7 +311,7 @@ async function main() {
272
311
  console.log('\x1b[32m✓ Password moved to keychain.\x1b[0m\n');
273
312
  }
274
313
  else {
275
- console.log('\x1b[31mCould not access keychain; leaving the file unchanged.\x1b[0m\n');
314
+ console.log('\x1b[31mCould not move the password; the file still holds it.\x1b[0m\n');
276
315
  }
277
316
  }
278
317
  }
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { styleText } from 'node:util';
4
+ import { Box, useStdout } from 'ink';
5
+ import { StatusIndicator } from '../components/StatusIndicator.js';
6
+ import { useTasks } from '../utils/tasks.js';
7
+ import { useFinish } from './use-finish.js';
8
+ const LEVEL_STYLE = { info: 'reset', ok: 'green', warn: 'yellow', error: 'red' };
9
+ /**
10
+ * A one-shot command: the task's log printed above the status as it comes (a
11
+ * terminal keeps it in scrollback, CI keeps all of it), then exit with its result.
12
+ */
13
+ export function TaskScreen({ task, stay = false, children }) {
14
+ const { write } = useStdout();
15
+ const live = useTasks().find(t => t.id === task.id) ?? task;
16
+ const result = live.status === 'running'
17
+ ? undefined
18
+ : live.status === 'success'
19
+ ? 'success'
20
+ : 'error';
21
+ // ponytail: counts lines, so past the task's 2000-line cap (where old lines
22
+ // are dropped) some would be skipped. No one-shot command logs that much.
23
+ const printed = useRef(0);
24
+ useEffect(() => {
25
+ for (const line of live.lines.slice(printed.current)) {
26
+ write(`${styleText('dim', line.tag.padEnd(3))} ${styleText(LEVEL_STYLE[line.level], line.text)}\n`);
27
+ }
28
+ printed.current = live.lines.length;
29
+ });
30
+ useFinish(result, stay && result === 'error');
31
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(StatusIndicator, { status: result ?? 'running', label: live.phase, message: `${live.label} ${live.appName}` }), children] }));
32
+ }
@@ -1,192 +1,13 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { render, Box, Text, useInput } from 'ink';
4
- import { StatusIndicator } from '../components/StatusIndicator.js';
5
- import { WebpackRunner, hasLocalWebpackConfig } from '../utils/webpack-runner.js';
6
- import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from '../utils/sitevision-scripts-runner.js';
7
- import { copyStaticToBuild, copySrcToBuild, cleanBuild, formatFileSize, createBuildZip, getZipSize, zipExists, } from '../utils/zip.js';
8
- import { isBundledApp, getAppType, getFullAppId, } from '../utils/project-detection.js';
9
- export function BuildScreen({ projectRoot, manifest, createZip = true, onBack, }) {
10
- const [state, setState] = React.useState({
11
- status: 'cleaning',
12
- message: 'Cleaning build directory...',
13
- });
14
- useInput((input, key) => {
15
- if (onBack &&
16
- (key.escape || input === 'q') &&
17
- (state.status === 'success' || state.status === 'error')) {
18
- onBack();
19
- }
20
- });
21
- const isBundled = isBundledApp(manifest);
22
- React.useEffect(() => {
23
- async function runBuild() {
24
- try {
25
- // Step 1: Clean build directory
26
- setState({ status: 'cleaning', message: 'Cleaning build directory...' });
27
- cleanBuild(projectRoot);
28
- // Step 2: Build or copy files
29
- if (isBundled && !hasLocalWebpackConfig(projectRoot)) {
30
- // No project-local webpack config: delegate the whole build to
31
- // @sitevision/sitevision-scripts, which compiles + zips to
32
- // dist/<appId>.zip (the path the CLI's sign/deploy already use).
33
- if (!hasSitevisionScripts(projectRoot)) {
34
- setState({
35
- status: 'error',
36
- error: 'No webpack.config.js found and @sitevision/sitevision-scripts is not installed. Run npm install.',
37
- });
38
- return;
39
- }
40
- // Warn if the installed sitevision-scripts is outside the range
41
- // the delegated build was validated against.
42
- const warning = checkSitevisionScriptsCompatibility(projectRoot).warning;
43
- setState({
44
- status: 'building',
45
- message: 'Building via sitevision-scripts...',
46
- warning,
47
- });
48
- const sitevisionResult = await runSitevisionScriptsBuild(projectRoot);
49
- if (!sitevisionResult.success) {
50
- setState({
51
- status: 'error',
52
- error: `${sitevisionResult.error}\n${sitevisionResult.output.slice(-1000)}`,
53
- warning,
54
- });
55
- return;
56
- }
57
- // sitevision-scripts already produced the zip; report it directly.
58
- const zipPath = getDelegatedZipPath(projectRoot, manifest.id);
59
- if (!zipExists(zipPath)) {
60
- setState({
61
- status: 'error',
62
- error: `Build reported success but no zip was found at ${zipPath}.`,
63
- warning,
64
- });
65
- return;
66
- }
67
- setState({
68
- status: 'success',
69
- message: 'Build complete',
70
- zipPath,
71
- zipSize: getZipSize(zipPath),
72
- warning,
73
- });
74
- return;
75
- }
76
- if (isBundled) {
77
- // Project ships its own webpack config: build it in-process.
78
- if (!WebpackRunner.isWebpackAvailable(projectRoot)) {
79
- setState({
80
- status: 'error',
81
- error: 'webpack not found. Run npm install to install dependencies.',
82
- });
83
- return;
84
- }
85
- setState({ status: 'building', message: 'Compiling with webpack...' });
86
- const appType = getAppType(manifest);
87
- const runner = new WebpackRunner(projectRoot, {
88
- mode: 'production',
89
- cssPrefix: manifest.id,
90
- restApp: appType !== 'web' && appType !== 'widget',
91
- });
92
- const result = await runner.run();
93
- await runner.close();
94
- if (!result.success) {
95
- setState({
96
- status: 'error',
97
- error: result.errors?.join('\n') || 'Build failed',
98
- result,
99
- });
100
- return;
101
- }
102
- // Copy static files after webpack build
103
- setState({ status: 'copying', message: 'Copying static files...' });
104
- copyStaticToBuild(projectRoot);
105
- setState(prev => ({ ...prev, result }));
106
- }
107
- else {
108
- // Non-bundled app: just copy files
109
- setState({ status: 'copying', message: 'Copying source files...' });
110
- copySrcToBuild(projectRoot);
111
- copyStaticToBuild(projectRoot);
112
- }
113
- // Step 3: Create zip if requested
114
- if (createZip) {
115
- setState({ status: 'zipping', message: 'Creating zip archive...' });
116
- const appId = getFullAppId(manifest.id);
117
- const zipPath = await createBuildZip(projectRoot, appId);
118
- const zipSize = getZipSize(zipPath);
119
- setState(prev => ({
120
- ...prev,
121
- status: 'success',
122
- message: 'Build complete',
123
- zipPath,
124
- zipSize,
125
- }));
126
- }
127
- else {
128
- setState(prev => ({
129
- ...prev,
130
- status: 'success',
131
- message: 'Build complete',
132
- }));
133
- }
134
- }
135
- catch (error) {
136
- setState({
137
- status: 'error',
138
- error: error instanceof Error ? error.message : String(error),
139
- });
140
- }
141
- }
142
- runBuild();
143
- }, [projectRoot, manifest, isBundled, createZip]);
144
- const getStatusIndicator = () => {
145
- switch (state.status) {
146
- case 'cleaning':
147
- case 'building':
148
- case 'copying':
149
- case 'zipping':
150
- return 'running';
151
- case 'success':
152
- return 'success';
153
- case 'error':
154
- return 'error';
155
- }
156
- };
157
- const getStatusLabel = () => {
158
- switch (state.status) {
159
- case 'cleaning':
160
- return 'Cleaning';
161
- case 'building':
162
- return 'Building';
163
- case 'copying':
164
- return 'Copying files';
165
- case 'zipping':
166
- return 'Creating zip';
167
- case 'success':
168
- return 'Build complete';
169
- case 'error':
170
- return 'Build failed';
171
- }
172
- };
173
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(StatusIndicator, { status: getStatusIndicator(), label: getStatusLabel(), message: state.message }) }), state.warning && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", state.warning] }) })), state.status === 'success' && state.result?.stats && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { dimColor: true, children: ["Compiled in ", state.result.stats.time, "ms"] }), state.result.stats.assets &&
174
- state.result.stats.assets.length > 0 && (_jsxs(Text, { dimColor: true, children: ["Assets: ", state.result.stats.assets.join(', ')] }))] })), state.status === 'success' && state.zipPath && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["\u2713 Created: ", state.zipPath] }), state.zipSize !== undefined && (_jsxs(Text, { dimColor: true, children: [" Size: ", formatFileSize(state.zipSize)] }))] })), state.result?.warnings && state.result.warnings.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", children: "Warnings:" }), state.result.warnings.slice(0, 5).map((warning, i) => (_jsx(Text, { color: "yellow", dimColor: true, children: warning.substring(0, 200) }, i))), state.result.warnings.length > 5 && (_jsxs(Text, { color: "yellow", dimColor: true, children: ["...and ", state.result.warnings.length - 5, " more"] }))] })), state.status === 'error' && state.error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "red", children: state.error }) })), onBack && (state.status === 'success' || state.status === 'error') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press q or Esc to return to menu" }) }))] }));
175
- }
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { TaskScreen } from './TaskScreen.js';
4
+ import { startBuild } from '../utils/tasks.js';
176
5
  export const buildCommand = {
177
6
  name: 'build',
178
7
  description: 'Build the application for production',
179
8
  requiresProject: true,
180
- flags: {
181
- 'no-zip': {
182
- type: 'boolean',
183
- description: 'Skip creating the zip archive',
184
- default: false,
185
- },
186
- },
187
9
  async execute({ project, flags }) {
188
- const createZip = !flags['no-zip'];
189
- const { waitUntilExit } = render(_jsx(BuildScreen, { projectRoot: project.root, manifest: project.manifest, createZip: createZip }));
190
- await waitUntilExit();
10
+ const task = startBuild(project, { zip: flags['zip'] !== false });
11
+ await render(_jsx(TaskScreen, { task: task })).waitUntilExit();
191
12
  },
192
13
  };