sitevision-cli 1.0.0-beta.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/LICENSE +21 -0
- package/dist/cli.js +194 -70
- package/dist/commands/TaskScreen.js +32 -0
- package/dist/commands/build.js +6 -185
- package/dist/commands/deploy.js +95 -212
- package/dist/commands/dev.js +87 -416
- package/dist/commands/info.js +1 -1
- package/dist/commands/setup-signing.js +50 -33
- package/dist/commands/sign.js +9 -86
- package/dist/commands/use-finish.js +17 -0
- package/dist/commands/watch.js +5 -31
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.js +33 -15
- package/dist/components/PasswordInput.js +2 -1
- package/dist/scaffold/inquirer-stub.js +119 -0
- package/dist/scaffold/register.js +15 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/Changelog.js +44 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.js +703 -0
- package/dist/shell/Frame.js +138 -0
- package/dist/shell/Help.js +96 -0
- package/dist/shell/Popover.js +15 -0
- package/dist/shell/Settings.js +149 -0
- package/dist/shell/Shell.js +953 -0
- package/dist/shell/Tabs.js +96 -0
- package/dist/shell/actions.js +493 -0
- package/dist/utils/config.js +62 -2
- package/dist/utils/create-app.js +231 -0
- package/dist/utils/debug.js +32 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.js +369 -0
- package/dist/utils/jsonc.js +2 -1
- package/dist/utils/keychain.js +54 -6
- package/dist/utils/oauth2-auth.js +146 -43
- package/dist/utils/password-prompt.js +37 -3
- package/dist/utils/process-runner.js +77 -38
- package/dist/utils/project-detection.js +457 -112
- package/dist/utils/session-cookie-auth.js +10 -6
- package/dist/utils/sitevision-api.js +134 -25
- package/dist/utils/sitevision-scripts-runner.js +50 -22
- package/dist/utils/tasks.js +510 -0
- package/dist/utils/version-check.js +3 -3
- package/dist/utils/webpack-runner.js +9 -5
- package/dist/utils/workspace.js +105 -0
- package/dist/utils/zip.js +18 -19
- package/package.json +27 -4
- package/readme.md +113 -141
- package/dist/app.d.ts +0 -6
- package/dist/app.js +0 -307
- package/dist/cli.d.ts +0 -2
- package/dist/commands/build.d.ts +0 -12
- package/dist/commands/deploy.d.ts +0 -16
- package/dist/commands/dev.d.ts +0 -16
- package/dist/commands/index.d.ts +0 -4
- package/dist/commands/info.d.ts +0 -2
- package/dist/commands/setup-signing.d.ts +0 -2
- package/dist/commands/sign.d.ts +0 -14
- package/dist/commands/types.d.ts +0 -18
- package/dist/commands/watch.d.ts +0 -2
- package/dist/components/AnimatedLogo.d.ts +0 -10
- package/dist/components/AuthLoginScreen.d.ts +0 -21
- package/dist/components/DevPropertiesForm.d.ts +0 -10
- package/dist/components/DevPropertiesForm.js +0 -210
- package/dist/components/InfoScreen.d.ts +0 -7
- package/dist/components/InfoScreen.js +0 -14
- package/dist/components/KeychainPasswordChoice.d.ts +0 -8
- package/dist/components/KeychainPasswordChoice.js +0 -30
- package/dist/components/MainMenu.d.ts +0 -7
- package/dist/components/MainMenu.js +0 -83
- package/dist/components/PasswordInput.d.ts +0 -10
- package/dist/components/ProcessOutput.d.ts +0 -6
- package/dist/components/ProcessOutput.js +0 -30
- package/dist/components/SetupFlow.d.ts +0 -8
- package/dist/components/SetupFlow.js +0 -233
- package/dist/components/SigningPropertiesForm.d.ts +0 -7
- package/dist/components/SigningPropertiesForm.js +0 -38
- package/dist/components/StatusIndicator.d.ts +0 -8
- package/dist/components/TextInput.d.ts +0 -10
- package/dist/components/TextInput.js +0 -29
- package/dist/components/WelcomeScreen.d.ts +0 -14
- package/dist/types/index.d.ts +0 -284
- package/dist/utils/branding.d.ts +0 -28
- package/dist/utils/config.d.ts +0 -17
- package/dist/utils/jsonc.d.ts +0 -19
- package/dist/utils/keychain.d.ts +0 -15
- package/dist/utils/oauth2-auth.d.ts +0 -27
- package/dist/utils/password-prompt.d.ts +0 -14
- package/dist/utils/process-runner.d.ts +0 -22
- package/dist/utils/project-detection.d.ts +0 -151
- package/dist/utils/session-cookie-auth.d.ts +0 -34
- package/dist/utils/signing-password.d.ts +0 -12
- package/dist/utils/signing-step.d.ts +0 -15
- package/dist/utils/signing-step.js +0 -16
- package/dist/utils/sitevision-api.d.ts +0 -112
- package/dist/utils/sitevision-scripts-runner.d.ts +0 -83
- package/dist/utils/version-check.d.ts +0 -9
- package/dist/utils/webpack-runner.d.ts +0 -76
- package/dist/utils/zip.d.ts +0 -71
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
First stable release. Everything from the betas is in, see the entries below.
|
|
6
|
+
|
|
7
|
+
- `svc --debug` (or `SVC_DEBUG=1`) writes a log of everything svc does during
|
|
8
|
+
the run: each action, task and its output, request to Sitevision, child
|
|
9
|
+
process, config change, prompt and keychain lookup. The path is printed on
|
|
10
|
+
exit. Passwords, tokens and cookies are never written; the site, username
|
|
11
|
+
and file paths are, so read it before attaching it to a bug report.
|
|
12
|
+
- Bug reports and feature requests have forms on GitHub.
|
|
13
|
+
|
|
14
|
+
## 1.0.0-beta.33
|
|
15
|
+
|
|
16
|
+
- **Breaking:** `--token` and `--cookie` are removed, since credentials on the
|
|
17
|
+
command line end up in shell history. Use `SITEVISION_ACCESS_TOKEN` or
|
|
18
|
+
`SITEVISION_SESSION_COOKIE` instead.
|
|
19
|
+
- **Breaking:** an unknown flag is now an error (exit code 2), so a misspelt
|
|
20
|
+
`--production` never turns into a dev deploy.
|
|
21
|
+
- `svc build`, `svc sign` and `svc deploy` work without a terminal. They print
|
|
22
|
+
their log line by line and exit on their own with a proper exit code: 0 on
|
|
23
|
+
success, 1 on failure, 130 when a prompt is cancelled. `svc dev` and
|
|
24
|
+
`svc watch` no longer crash when stdin is a pipe.
|
|
25
|
+
- New flags: `--no-zip` for `build`, and the short forms `-s`, `-f`, `-p` and
|
|
26
|
+
`-a`.
|
|
27
|
+
- Global settings in `~/.config/sitevision-cli/config.json`: a default signing
|
|
28
|
+
user and certificate for every project that does not set its own, and
|
|
29
|
+
switches for the update check and the addon name warning. Edit them with `,`
|
|
30
|
+
in the shell, which now shows a description of each setting and opens from
|
|
31
|
+
anywhere, or run `svc setup-signing --global`. A file that does not parse is
|
|
32
|
+
reported and left alone.
|
|
33
|
+
- The Config tab marks the addon name with `≠ manifest` when it matches none
|
|
34
|
+
of the manifest's names. Nothing is changed for you; the two are different
|
|
35
|
+
things and `svc` only points the difference out.
|
|
36
|
+
- The manifest's name and description always have a Swedish row in the Config
|
|
37
|
+
tab. Filling it in on a plain name keeps the old text as the English one.
|
|
38
|
+
- In a workspace, an environment's addon name is stored per app. Setting it
|
|
39
|
+
for one app no longer leaks into the others.
|
|
40
|
+
- `svc` names the missing setting when the deploy config is incomplete,
|
|
41
|
+
instead of posting to "undefined".
|
|
42
|
+
- A production deploy with `--activate` fails if the activation did not
|
|
43
|
+
happen, and the confirmation names the addon it deploys to.
|
|
44
|
+
- OAuth2 requires https and a token endpoint on the site's own domain. A
|
|
45
|
+
refresh token is only thrown away when the site rejects it, not on a network
|
|
46
|
+
error. An expired session cookie is removed so the next deploy asks for a
|
|
47
|
+
new login.
|
|
48
|
+
- A plaintext password in `.dev_properties.json` is moved to the keychain when
|
|
49
|
+
the file is saved, and `svc` says so when the keychain cannot store a secret.
|
|
50
|
+
`SVC_NO_KEYCHAIN=1` turns the keychain off for CI.
|
|
51
|
+
- `svc dev` with a project's own webpack config no longer deploys a half-built
|
|
52
|
+
zip when several files change at once.
|
|
53
|
+
- Builds work when dependencies are hoisted to the workspace root, a
|
|
54
|
+
`manifest.json` in the project root is picked up, file names with å, ä and ö
|
|
55
|
+
survive in the zip, and `SITEVISION_APP_ID_PREFIX`/`_SUFFIX` apply to build,
|
|
56
|
+
sign and deploy alike.
|
|
57
|
+
- A broken app in a workspace is skipped with a message instead of stopping
|
|
58
|
+
the shell. The shell asks before quitting during app creation, the PROD
|
|
59
|
+
badge survives a narrow terminal, Esc cancels a confirm, and Ctrl-modified
|
|
60
|
+
keys never fire a shortcut.
|
|
61
|
+
- The update check is skipped without a terminal and when `CI` is set.
|
|
62
|
+
- Updated user guides in English and Swedish.
|
|
63
|
+
|
|
64
|
+
## 1.0.0-beta.32
|
|
65
|
+
|
|
66
|
+
- Sitevision CLI can now create new apps. **New app** in the command palette
|
|
67
|
+
runs Sitevision's own `create-sitevision-app` as a managed experience and
|
|
68
|
+
guides you through the setup: pick a name and a folder, then answer the
|
|
69
|
+
tool's questions right in `svc`. What your workspace already knows (domain,
|
|
70
|
+
site name, username) is filled in for you, and passwords never end up in a
|
|
71
|
+
file.
|
|
72
|
+
- When the app is created, `svc` fills in its manifest (the name, plus author
|
|
73
|
+
and help URL from the workspace's `package.json`) and offers to create the
|
|
74
|
+
addon on the site, so the app is ready for its first deploy.
|
|
75
|
+
- If the tool cannot be run this way, `svc` hands it the terminal and picks up
|
|
76
|
+
again when it is done.
|
|
77
|
+
- The Config tab has a MANIFEST section for editing `manifest.json`: id,
|
|
78
|
+
version, name, description, author and help URL, one row per language for
|
|
79
|
+
localized fields. Comments and formatting in the file are kept.
|
|
80
|
+
- `y` (sync `package.json`) now also mirrors the manifest's `version`,
|
|
81
|
+
`description` and `author` into the app's `package.json`. An app whose
|
|
82
|
+
`package.json` lacks them shows as out of sync until `y` is pressed.
|
|
83
|
+
|
|
84
|
+
## 1.0.0-beta.31
|
|
85
|
+
|
|
86
|
+
- The help panel (`?`) now starts with a short guide to the pane you are in:
|
|
87
|
+
what it shows, how the values relate, and which keys apply. The Config guide
|
|
88
|
+
explains how local, shared and workspace settings layer on top of each
|
|
89
|
+
other. The panel scrolls when it does not fit.
|
|
90
|
+
- Updated dependencies.
|
|
91
|
+
|
|
92
|
+
## 1.0.0-beta.30
|
|
93
|
+
|
|
94
|
+
- Dev and watch no longer rebuild on file events that change nothing, such as
|
|
95
|
+
editor metadata, swap files or `.DS_Store`. Ignored events are logged as
|
|
96
|
+
warnings so a stray trigger can be traced.
|
|
97
|
+
- The log names the event and path of the file that started a rebuild, for
|
|
98
|
+
example `change src/index.tsx`.
|
|
99
|
+
|
|
100
|
+
## 1.0.0-beta.29
|
|
101
|
+
|
|
102
|
+
- After an update, the shell opens What's new with the changes since the
|
|
103
|
+
version you last ran.
|
|
104
|
+
|
|
105
|
+
## 1.0.0-beta.28
|
|
106
|
+
|
|
107
|
+
## 1.0.0-beta.27
|
|
108
|
+
|
|
109
|
+
- Popups float over the screen instead of replacing the content pane: the
|
|
110
|
+
command palette, settings, addon picker, confirmations and prompts.
|
|
111
|
+
- `?` opens a help panel with every key, plus the keys for the current tab.
|
|
112
|
+
- What's new in the command palette shows this changelog.
|
|
113
|
+
|
|
114
|
+
## 1.0.0-beta.26
|
|
115
|
+
|
|
116
|
+
- No functional changes.
|
|
117
|
+
|
|
118
|
+
## 1.0.0-beta.25
|
|
119
|
+
|
|
120
|
+
- Deploying to an addon that does not exist yet now offers to create it and
|
|
121
|
+
deploy again. If the addon list cannot be read, the error suggests logging in
|
|
122
|
+
again instead, since an expired session fails the same way.
|
|
123
|
+
- A Create addon action appears in the command palette when a deploy has found
|
|
124
|
+
the addon missing.
|
|
125
|
+
- Creating a RESTApp or MCPServer addon no longer sends a category, which only
|
|
126
|
+
WebApp and widget addons use.
|
|
127
|
+
|
|
128
|
+
## 1.0.0-beta.24
|
|
129
|
+
|
|
130
|
+
- Deploying to a non-production environment uploads the signed zip when it is at
|
|
131
|
+
least as new as the build. The log names the zip that was uploaded.
|
|
132
|
+
- `x` in the Log tab wraps long lines immediately, and wrapped lines no longer
|
|
133
|
+
push the newest line out of view.
|
|
134
|
+
- Keys: switching environment moved from `E` to `v`, and refreshing versions
|
|
135
|
+
from `R` to `r`. Tab in the Config form moves between fields without leaving
|
|
136
|
+
the form.
|
|
137
|
+
- The bottom bar now shows force deploy (`P`), stop, config, login and settings.
|
|
138
|
+
- Running commands are stopped when `svc` exits.
|
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
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { render } from 'ink';
|
|
4
4
|
import { Text, Box } from 'ink';
|
|
5
5
|
import meow from 'meow';
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
|
-
import
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { runningTasks } from './utils/tasks.js';
|
|
10
|
+
import { killAllChildren } from './utils/process-runner.js';
|
|
11
|
+
import { Shell } from './shell/Shell.js';
|
|
8
12
|
import { getCommand } from './commands/index.js';
|
|
9
|
-
import { requireProject, migrateLegacyPassword, } from './utils/project-detection.js';
|
|
13
|
+
import { requireProject, detectProject, migrateLegacyPassword, } from './utils/project-detection.js';
|
|
14
|
+
import { discoverApps } from './utils/workspace.js';
|
|
10
15
|
import { promptYesNo } from './utils/password-prompt.js';
|
|
11
16
|
import { checkForUpdate } from './utils/version-check.js';
|
|
12
|
-
import { isFirstRun, markFirstRunComplete, getLastSeenVersion, setLastSeenVersion, } from './utils/config.js';
|
|
17
|
+
import { isFirstRun, markFirstRunComplete, getLastSeenVersion, setLastSeenVersion, getSettings, configProblem, settingsFile, } from './utils/config.js';
|
|
18
|
+
import { setLanguage } from './utils/i18n.js';
|
|
19
|
+
import { debug, enableDebug, errorText, say } from './utils/debug.js';
|
|
13
20
|
import { WelcomeScreen } from './components/WelcomeScreen.js';
|
|
14
21
|
import { AnimatedLogo } from './components/AnimatedLogo.js';
|
|
15
22
|
import { printBranding, BIG_LOGO, BIG_LOGO_WIDTH, SMALL_LOGO, SMALL_LOGO_WIDTH, } from './utils/branding.js';
|
|
16
23
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
17
24
|
const cli = meow(`
|
|
18
25
|
Usage
|
|
19
|
-
$ svc
|
|
26
|
+
$ svc Open the interactive shell (app or workspace)
|
|
20
27
|
$ svc <command> [options]
|
|
21
28
|
|
|
22
29
|
Commands
|
|
@@ -26,13 +33,26 @@ const cli = meow(`
|
|
|
26
33
|
sign Sign the app for production deployment
|
|
27
34
|
deploy Deploy the application
|
|
28
35
|
info Show project information
|
|
36
|
+
setup-signing Store the signing username and certificate
|
|
29
37
|
|
|
30
38
|
Options
|
|
31
|
-
--
|
|
32
|
-
--
|
|
39
|
+
--signed, -s dev/watch: sign after each build
|
|
40
|
+
--force, -f deploy: overwrite the existing addon version
|
|
41
|
+
--production, -p deploy: upload the signed zip to production
|
|
42
|
+
--activate, -a deploy: activate after a production deploy
|
|
43
|
+
--no-zip build: skip the zip archive
|
|
44
|
+
--global setup-signing: save for every project on this machine
|
|
45
|
+
--minimal Shell: compact layout for small terminals
|
|
46
|
+
--debug Write a log of every action to debug.log (or SVC_DEBUG=1)
|
|
47
|
+
--help Show this help message
|
|
48
|
+
--version Show version number
|
|
49
|
+
|
|
50
|
+
Credentials for CI come from the environment: SITEVISION_DEPLOY_PASSWORD,
|
|
51
|
+
SITEVISION_ACCESS_TOKEN or SITEVISION_SESSION_COOKIE.
|
|
33
52
|
|
|
34
53
|
Examples
|
|
35
|
-
$ svc #
|
|
54
|
+
$ svc # Shell: run inside an app, or at the repo root
|
|
55
|
+
$ svc --minimal # Shell without the sidebar, for a small pane
|
|
36
56
|
$ svc dev
|
|
37
57
|
$ svc dev --signed
|
|
38
58
|
$ svc watch
|
|
@@ -43,9 +63,20 @@ const cli = meow(`
|
|
|
43
63
|
$ svc info
|
|
44
64
|
`, {
|
|
45
65
|
importMeta: import.meta,
|
|
66
|
+
allowUnknownFlags: false,
|
|
46
67
|
flags: {
|
|
47
68
|
signed: {
|
|
48
69
|
type: 'boolean',
|
|
70
|
+
shortFlag: 's',
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
zip: {
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
default: true,
|
|
76
|
+
},
|
|
77
|
+
activate: {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
shortFlag: 'a',
|
|
49
80
|
default: false,
|
|
50
81
|
},
|
|
51
82
|
force: {
|
|
@@ -58,15 +89,34 @@ const cli = meow(`
|
|
|
58
89
|
shortFlag: 'p',
|
|
59
90
|
default: false,
|
|
60
91
|
},
|
|
61
|
-
|
|
62
|
-
type: '
|
|
92
|
+
minimal: {
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
default: false,
|
|
95
|
+
},
|
|
96
|
+
global: {
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
default: false,
|
|
63
99
|
},
|
|
64
|
-
|
|
65
|
-
type: '
|
|
100
|
+
debug: {
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
default: false,
|
|
66
103
|
},
|
|
67
104
|
},
|
|
68
105
|
});
|
|
69
106
|
const [commandName, ...args] = cli.input;
|
|
107
|
+
const settings = getSettings();
|
|
108
|
+
if (cli.flags.debug || process.env['SVC_DEBUG']) {
|
|
109
|
+
const file = enableDebug(path.dirname(settingsFile()));
|
|
110
|
+
process.on('exit', code => {
|
|
111
|
+
debug('exit', `code ${code}`);
|
|
112
|
+
process.stderr.write(`\x1b[2mDebug log: ${file}\x1b[0m\n`);
|
|
113
|
+
});
|
|
114
|
+
const on = Object.entries(cli.flags)
|
|
115
|
+
.filter(([, v]) => v === true)
|
|
116
|
+
.map(([k]) => `--${k}`);
|
|
117
|
+
debug('start', `svc ${pkg.version} node ${process.version} ${process.platform} ${process.arch} ${[commandName ?? '(shell)', ...args, ...on].join(' ')}`);
|
|
118
|
+
}
|
|
119
|
+
setLanguage(settings.language);
|
|
70
120
|
const CYAN = '\x1b[36m';
|
|
71
121
|
const BOLD = '\x1b[1m';
|
|
72
122
|
const DIM = '\x1b[2m';
|
|
@@ -95,8 +145,27 @@ function printMasthead(version) {
|
|
|
95
145
|
`${spaces(gap)}${DIM}${right}${RESET}${spaces(padding)}${CYAN}│${RESET}`);
|
|
96
146
|
console.log(`${CYAN}╰${border}╯${RESET}`);
|
|
97
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Stop every running task and kill leftover child processes. In raw mode Ink
|
|
150
|
+
* reads Ctrl+C as a keypress, so no SIGINT reaches anything else. Exits when
|
|
151
|
+
* there was work to stop (or on a signal); otherwise the process ends on its
|
|
152
|
+
* own, so a command's last output is never cut off.
|
|
153
|
+
*/
|
|
154
|
+
function shutdown(code) {
|
|
155
|
+
const busy = runningTasks();
|
|
156
|
+
for (const task of busy)
|
|
157
|
+
task.stop();
|
|
158
|
+
const killed = killAllChildren();
|
|
159
|
+
if (busy.length > 0 || killed > 0 || code !== undefined)
|
|
160
|
+
process.exit(code);
|
|
161
|
+
}
|
|
162
|
+
function fail(message, hint) {
|
|
163
|
+
debug('out', `Error: ${message}`);
|
|
164
|
+
render(_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { color: "red", children: ["Error: ", message] }), _jsx(Text, { dimColor: true, children: hint })] }));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
98
167
|
// Pick the widest wordmark that fits the terminal, or undefined if even the
|
|
99
|
-
// compact one would wrap
|
|
168
|
+
// compact one would wrap.
|
|
100
169
|
function pickIntroArt(columns) {
|
|
101
170
|
if (columns >= BIG_LOGO_WIDTH)
|
|
102
171
|
return BIG_LOGO;
|
|
@@ -111,7 +180,64 @@ async function playIntro(art) {
|
|
|
111
180
|
app.waitUntilExit().then(() => resolve(), () => resolve());
|
|
112
181
|
});
|
|
113
182
|
}
|
|
183
|
+
// Run the full-screen shell on the alternate screen buffer so the scrollback
|
|
184
|
+
// is untouched, and restore it on exit. The animated wordmark plays first,
|
|
185
|
+
// inside the same buffer, when the terminal is wide enough for it.
|
|
186
|
+
async function runShell(apps, workspaceRoot, updatedFrom, skipped = []) {
|
|
187
|
+
process.stdout.write('\x1b[?1049h\x1b[H');
|
|
188
|
+
// Also leave the alternate screen when a signal exits past the finally.
|
|
189
|
+
process.prependOnceListener('exit', () => process.stdout.write('\x1b[?1049l'));
|
|
190
|
+
// A crash outside React would print into the alternate screen and vanish
|
|
191
|
+
// with it: leave it first, then report.
|
|
192
|
+
const crash = (error) => {
|
|
193
|
+
process.stdout.write('\x1b[?1049l');
|
|
194
|
+
debug('crash', errorText(error));
|
|
195
|
+
console.error('svc crashed:', error);
|
|
196
|
+
shutdown(1);
|
|
197
|
+
};
|
|
198
|
+
process.once('uncaughtException', crash);
|
|
199
|
+
process.once('unhandledRejection', crash);
|
|
200
|
+
try {
|
|
201
|
+
const art = process.stdin.isTTY && settings.introAnimation && !cli.flags.minimal
|
|
202
|
+
? pickIntroArt(process.stdout.columns ?? 0)
|
|
203
|
+
: undefined;
|
|
204
|
+
if (art) {
|
|
205
|
+
await playIntro(art);
|
|
206
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
207
|
+
}
|
|
208
|
+
// A handover unmounts the shell, runs its job on the normal screen with
|
|
209
|
+
// the terminal to itself, and starts the shell again.
|
|
210
|
+
for (;;) {
|
|
211
|
+
let job;
|
|
212
|
+
const app = render(_jsx(Shell, { apps: apps, workspaceRoot: workspaceRoot, version: pkg.version, minimal: cli.flags.minimal, updatedFrom: updatedFrom, skipped: skipped, handover: next => {
|
|
213
|
+
job = next;
|
|
214
|
+
app.unmount();
|
|
215
|
+
} }));
|
|
216
|
+
// eslint-disable-next-line no-await-in-loop
|
|
217
|
+
await app.waitUntilExit();
|
|
218
|
+
if (!job)
|
|
219
|
+
break;
|
|
220
|
+
process.stdout.write('\x1b[?1049l');
|
|
221
|
+
// eslint-disable-next-line no-await-in-loop
|
|
222
|
+
await job();
|
|
223
|
+
if (workspaceRoot) {
|
|
224
|
+
skipped = [];
|
|
225
|
+
apps = discoverApps(workspaceRoot, skipped);
|
|
226
|
+
}
|
|
227
|
+
updatedFrom = undefined;
|
|
228
|
+
process.stdout.write('\x1b[?1049h\x1b[2J\x1b[H');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
process.stdout.write('\x1b[?1049l');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
114
235
|
async function main() {
|
|
236
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
237
|
+
process.on(signal, () => {
|
|
238
|
+
shutdown(128 + os.constants.signals[signal]);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
115
241
|
// On the very first run we show a dedicated welcome screen instead of the
|
|
116
242
|
// masthead, so the branding is the moment. Only when stdin is a TTY — the
|
|
117
243
|
// welcome is interactive and would hang in CI / piped input.
|
|
@@ -122,106 +248,104 @@ async function main() {
|
|
|
122
248
|
// silently rather than claiming an update happened.
|
|
123
249
|
const lastSeen = getLastSeenVersion();
|
|
124
250
|
const isUpdate = !firstRun && lastSeen !== undefined && lastSeen !== pkg.version;
|
|
125
|
-
// On the plain interactive `svc` (no command), play the animated wordmark
|
|
126
|
-
// instead of the static masthead — sized to the terminal. Only on a TTY so it
|
|
127
|
-
// doesn't run in CI / piped input.
|
|
128
|
-
const introEligible = !firstRun && !isUpdate && !commandName && Boolean(process.stdin.isTTY);
|
|
129
|
-
const introArt = introEligible
|
|
130
|
-
? pickIntroArt(process.stdout.columns ?? 0)
|
|
131
|
-
: undefined;
|
|
132
251
|
if (!firstRun) {
|
|
133
|
-
|
|
252
|
+
// The shell shows the changelog itself; only direct commands get a banner.
|
|
253
|
+
if (isUpdate && commandName) {
|
|
134
254
|
printBranding();
|
|
135
|
-
|
|
255
|
+
say(`\x1b[32m\n ✨ Updated to v${pkg.version}\x1b[0m \x1b[2m(from v${lastSeen})\x1b[0m\n`);
|
|
136
256
|
}
|
|
137
|
-
else if (
|
|
257
|
+
else if (commandName) {
|
|
138
258
|
printMasthead(pkg.version);
|
|
139
259
|
}
|
|
140
260
|
// Record the current version so the banner shows once per upgrade.
|
|
141
261
|
setLastSeenVersion(pkg.version);
|
|
262
|
+
if (configProblem()) {
|
|
263
|
+
say(`\x1b[33m ${settingsFile()} does not parse and is ignored: ${configProblem()}\x1b[0m`);
|
|
264
|
+
}
|
|
142
265
|
// Check npm for a newer published release.
|
|
143
|
-
const latestVersion =
|
|
266
|
+
const latestVersion = settings.updateCheck && process.stdout.isTTY && !process.env['CI']
|
|
267
|
+
? await checkForUpdate(pkg.name, pkg.version)
|
|
268
|
+
: null;
|
|
144
269
|
if (latestVersion) {
|
|
145
|
-
|
|
270
|
+
say(`\x1b[33m ↑ update available: ${pkg.version} → ${latestVersion} (run: npm i -g ${pkg.name})\x1b[0m`);
|
|
146
271
|
}
|
|
147
272
|
}
|
|
148
|
-
//
|
|
149
|
-
|
|
273
|
+
// No command: the shell. Inside an app it is single-app mode; anywhere
|
|
274
|
+
// above one or more apps it is workspace mode.
|
|
275
|
+
if (!commandName) {
|
|
276
|
+
let project = null;
|
|
150
277
|
try {
|
|
151
|
-
|
|
278
|
+
project = detectProject();
|
|
152
279
|
}
|
|
153
280
|
catch (error) {
|
|
154
|
-
|
|
155
|
-
process.exit(1);
|
|
281
|
+
fail(error.message, 'Fix the manifest and try again');
|
|
156
282
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (cli.flags.token) {
|
|
164
|
-
project.devProperties.accessToken = cli.flags.token;
|
|
165
|
-
}
|
|
166
|
-
if (cli.flags.cookie) {
|
|
167
|
-
project.devProperties.sessionCookie = cli.flags.cookie;
|
|
283
|
+
if (!project) {
|
|
284
|
+
const skipped = [];
|
|
285
|
+
const apps = discoverApps(process.cwd(), skipped);
|
|
286
|
+
if (apps.length === 0) {
|
|
287
|
+
fail('No Sitevision apps found here.', skipped[0] ??
|
|
288
|
+
'Run svc inside an app directory (manifest.json) or at the root of a repo that contains apps.');
|
|
168
289
|
}
|
|
290
|
+
await runShell(apps, process.cwd(), isUpdate ? lastSeen : undefined, skipped);
|
|
291
|
+
shutdown();
|
|
292
|
+
return;
|
|
169
293
|
}
|
|
170
|
-
|
|
171
|
-
|
|
294
|
+
if (firstRun) {
|
|
295
|
+
await new Promise(resolve => {
|
|
296
|
+
const app = render(_jsx(WelcomeScreen, { project: project, onComplete: () => {
|
|
297
|
+
markFirstRunComplete();
|
|
298
|
+
setLastSeenVersion(pkg.version);
|
|
299
|
+
app.unmount();
|
|
300
|
+
} }));
|
|
301
|
+
app.waitUntilExit().then(() => resolve(), () => resolve());
|
|
302
|
+
});
|
|
172
303
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// then continue to the normal flow once the user dismisses it.
|
|
176
|
-
if (firstRun) {
|
|
177
|
-
await new Promise(resolve => {
|
|
178
|
-
const app = render(_jsx(WelcomeScreen, { project: project, onComplete: () => {
|
|
179
|
-
markFirstRunComplete();
|
|
180
|
-
setLastSeenVersion(pkg.version);
|
|
181
|
-
app.unmount();
|
|
182
|
-
} }));
|
|
183
|
-
app.waitUntilExit().then(() => resolve(), () => resolve());
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
// If no command, show interactive menu (with the animated intro first when
|
|
187
|
-
// the terminal can fit it).
|
|
188
|
-
if (!commandName) {
|
|
189
|
-
if (introArt) {
|
|
190
|
-
await playIntro(introArt);
|
|
191
|
-
}
|
|
192
|
-
render(_jsx(App, { project: project }));
|
|
304
|
+
await runShell([project], undefined, isUpdate ? lastSeen : undefined);
|
|
305
|
+
shutdown();
|
|
193
306
|
return;
|
|
194
307
|
}
|
|
308
|
+
// Check if we're in a Sitevision project
|
|
309
|
+
const project = (() => {
|
|
310
|
+
try {
|
|
311
|
+
return requireProject();
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return fail(error.message, "Make sure you're in a Sitevision project directory");
|
|
315
|
+
}
|
|
316
|
+
})();
|
|
195
317
|
// Get the command
|
|
196
318
|
const command = getCommand(commandName);
|
|
197
319
|
if (!command) {
|
|
320
|
+
debug('out', `Unknown command: ${commandName}`);
|
|
198
321
|
render(_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { color: "red", children: ["Unknown command: ", commandName] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { bold: true, children: "svc --help" }), " to see available commands"] })] }));
|
|
199
322
|
process.exit(1);
|
|
200
323
|
}
|
|
201
324
|
// Offer to migrate a legacy plaintext password into the OS keychain.
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
// would fail — the plaintext password is still used for this run.
|
|
325
|
+
// Skip on non-TTY stdin (e.g. CI) where prompting would fail — the
|
|
326
|
+
// plaintext password is still used for this run.
|
|
205
327
|
if (project.hasLegacyPassword && process.stdin.isTTY) {
|
|
206
|
-
|
|
328
|
+
say('\n\x1b[33m⚠ Plaintext password found in .dev_properties.json\x1b[0m');
|
|
207
329
|
const move = await promptYesNo('Move it to the OS keychain and remove it from the file? (y/N): ');
|
|
208
330
|
if (move) {
|
|
209
331
|
if (migrateLegacyPassword(project)) {
|
|
210
|
-
|
|
332
|
+
say('\x1b[32m✓ Password moved to keychain.\x1b[0m\n');
|
|
211
333
|
}
|
|
212
334
|
else {
|
|
213
|
-
|
|
335
|
+
say('\x1b[31mCould not move the password; the file still holds it.\x1b[0m\n');
|
|
214
336
|
}
|
|
215
337
|
}
|
|
216
338
|
}
|
|
217
|
-
|
|
339
|
+
debug('command', commandName);
|
|
218
340
|
await command.execute({
|
|
219
341
|
project,
|
|
220
342
|
flags: cli.flags,
|
|
221
343
|
args,
|
|
222
344
|
});
|
|
345
|
+
shutdown();
|
|
223
346
|
}
|
|
224
347
|
main().catch(error => {
|
|
348
|
+
debug('crash', errorText(error));
|
|
225
349
|
console.error('Fatal error:', error);
|
|
226
350
|
process.exit(1);
|
|
227
351
|
});
|
|
@@ -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
|
+
}
|