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.
- package/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/dist/cli.js +73 -34
- package/dist/commands/TaskScreen.js +32 -0
- package/dist/commands/build.js +6 -185
- package/dist/commands/deploy.js +85 -238
- package/dist/commands/dev.js +34 -37
- package/dist/commands/info.js +1 -1
- package/dist/commands/setup-signing.js +43 -28
- package/dist/commands/sign.js +9 -86
- package/dist/commands/use-finish.js +17 -0
- package/dist/commands/watch.js +0 -8
- package/dist/components/AuthLoginScreen.js +27 -12
- package/dist/scaffold/inquirer-stub.js +119 -0
- package/dist/scaffold/register.js +15 -0
- package/dist/shell/ConfigForm.js +174 -29
- package/dist/shell/Frame.js +4 -1
- package/dist/shell/Help.js +2 -1
- package/dist/shell/Settings.js +59 -6
- package/dist/shell/Shell.js +361 -43
- package/dist/shell/Tabs.js +8 -2
- package/dist/shell/actions.js +0 -0
- package/dist/utils/config.js +46 -3
- package/dist/utils/create-app.js +226 -0
- package/dist/utils/i18n.js +68 -13
- package/dist/utils/jsonc.js +2 -1
- package/dist/utils/keychain.js +44 -4
- package/dist/utils/oauth2-auth.js +35 -7
- package/dist/utils/password-prompt.js +23 -3
- package/dist/utils/process-runner.js +8 -3
- package/dist/utils/project-detection.js +172 -44
- package/dist/utils/session-cookie-auth.js +5 -4
- package/dist/utils/sitevision-api.js +21 -14
- package/dist/utils/sitevision-scripts-runner.js +32 -15
- package/dist/utils/tasks.js +132 -66
- package/dist/utils/version-check.js +3 -3
- package/dist/utils/webpack-runner.js +6 -5
- package/dist/utils/workspace.js +41 -3
- package/dist/utils/zip.js +18 -19
- package/package.json +24 -3
- package/readme.md +11 -4
- package/dist/app.d.ts +0 -6
- package/dist/app.js +0 -321
- package/dist/cli.d.ts +0 -2
- package/dist/commands/build.d.ts +0 -12
- package/dist/commands/deploy.d.ts +0 -17
- package/dist/commands/dev.d.ts +0 -14
- 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 -11
- package/dist/components/DevPropertiesForm.js +0 -246
- 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 -88
- 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/shell/AddonPicker.d.ts +0 -14
- package/dist/shell/Changelog.d.ts +0 -10
- package/dist/shell/CommandPalette.d.ts +0 -12
- package/dist/shell/ConfigForm.d.ts +0 -36
- package/dist/shell/Frame.d.ts +0 -59
- package/dist/shell/Help.d.ts +0 -9
- package/dist/shell/Popover.d.ts +0 -12
- package/dist/shell/Settings.d.ts +0 -6
- package/dist/shell/Shell.d.ts +0 -10
- package/dist/shell/Tabs.d.ts +0 -36
- package/dist/shell/actions.d.ts +0 -48
- package/dist/types/index.d.ts +0 -295
- package/dist/utils/branding.d.ts +0 -28
- package/dist/utils/config.d.ts +0 -27
- package/dist/utils/environments.d.ts +0 -20
- package/dist/utils/i18n.d.ts +0 -12
- package/dist/utils/jsonc.d.ts +0 -19
- package/dist/utils/keychain.d.ts +0 -15
- package/dist/utils/oauth2-auth.d.ts +0 -64
- package/dist/utils/password-prompt.d.ts +0 -14
- package/dist/utils/process-runner.d.ts +0 -31
- package/dist/utils/project-detection.d.ts +0 -202
- package/dist/utils/session-cookie-auth.d.ts +0 -35
- 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 -155
- package/dist/utils/sitevision-scripts-runner.d.ts +0 -84
- package/dist/utils/tasks.d.ts +0 -50
- package/dist/utils/version-check.d.ts +0 -9
- package/dist/utils/webpack-runner.d.ts +0 -76
- package/dist/utils/workspace.d.ts +0 -17
- package/dist/utils/zip.d.ts +0 -71
package/dist/shell/ConfigForm.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { findDevPropertiesPath, getPackageJsonSyncChanges, hasPackageJson, IMPLICIT_VALUES, normalizeDomain, readAncestorDevProperties, readInheritedDevProperties, readWorkspaceDevProperties, updatePackageJson, writeDevProperties, } from '../utils/project-detection.js';
|
|
4
|
+
import { addonNameDrift, findDevPropertiesPath, getPackageJsonSyncChanges, hasPackageJson, IMPLICIT_VALUES, normalizeDomain, readAncestorDevProperties, readInheritedDevProperties, readWorkspaceDevProperties, updatePackageJson, writeDevProperties, writeManifestField, } from '../utils/project-detection.js';
|
|
5
5
|
import { setDeployPassword, deleteDeployPassword, getOAuth2ClientSecret, setOAuth2ClientSecret, deleteOAuth2ClientSecret, getSigningPassword, setSigningPassword, deleteSigningPassword, } from '../utils/keychain.js';
|
|
6
6
|
import { DEFAULT_SCOPES, discoverOAuth2Config } from '../utils/oauth2-auth.js';
|
|
7
7
|
import { ACCENT } from './Frame.js';
|
|
8
8
|
import { baseEnvironment, withEnvironmentOverride, } from '../utils/environments.js';
|
|
9
9
|
import { t } from '../utils/i18n.js';
|
|
10
|
+
import { getGlobalSigning, getSettings } from '../utils/config.js';
|
|
10
11
|
const METHODS = ['basic', 'oauth2', 'cookie'];
|
|
11
12
|
const FIELDS = [
|
|
12
13
|
{
|
|
@@ -23,7 +24,7 @@ const FIELDS = [
|
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
key: 'addonName',
|
|
26
|
-
help: "Name of the addon (custom module) in the site's Addon Repository that this app is uploaded into. Ctrl+O lists the existing ones.",
|
|
27
|
+
help: "Name of the addon (custom module) in the site's Addon Repository that this app is uploaded into. Ctrl+O lists the existing ones. It is separate from the manifest name: to make them match, rename the addon in Sitevision (Addons, General, Settings) and pick it again with Ctrl+O. For a RESTApp the addon name is part of its endpoint URL.",
|
|
27
28
|
label: 'Addon name',
|
|
28
29
|
required: true,
|
|
29
30
|
hint: '^O pick from repo',
|
|
@@ -132,6 +133,67 @@ const FIELDS = [
|
|
|
132
133
|
hint: 'blank = prompt each run',
|
|
133
134
|
},
|
|
134
135
|
];
|
|
136
|
+
const MANIFEST_FIELDS = [
|
|
137
|
+
{
|
|
138
|
+
key: 'id',
|
|
139
|
+
label: 'App id',
|
|
140
|
+
required: true,
|
|
141
|
+
help: 'Identifier of the app in Sitevision; it also names the zip. Changing it makes the next deploy a new app instead of an update.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
key: 'version',
|
|
145
|
+
label: 'Version',
|
|
146
|
+
required: true,
|
|
147
|
+
help: 'Version of the app. Id and version together identify an upload, so bump it to deploy a new version instead of overwriting the current one.',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
key: 'name',
|
|
151
|
+
label: 'Name',
|
|
152
|
+
required: true,
|
|
153
|
+
help: 'Name shown when importing and administering the app in the Sitevision editor. A multilingual manifest needs at least English.',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
key: 'description',
|
|
157
|
+
label: 'Description',
|
|
158
|
+
help: 'Short description shown next to the name in the Sitevision editor.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
key: 'author',
|
|
162
|
+
label: 'Author',
|
|
163
|
+
help: 'Who made the app, shown in the Sitevision editor.',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: 'helpUrl',
|
|
167
|
+
label: 'Help URL',
|
|
168
|
+
help: "Link to the app's documentation, shown in the Sitevision editor.",
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
/** Rows for manifest.json: one per text field, one per language of a localized one. */
|
|
172
|
+
const LOCALIZABLE = new Set(['name', 'description']);
|
|
173
|
+
export function manifestFields(manifest) {
|
|
174
|
+
if (!manifest)
|
|
175
|
+
return [];
|
|
176
|
+
const raw = manifest;
|
|
177
|
+
return MANIFEST_FIELDS.flatMap(f => {
|
|
178
|
+
const value = raw[f.key];
|
|
179
|
+
const base = { ...f, section: 'MANIFEST', manifestKey: f.key };
|
|
180
|
+
const localized = (lang) => ({
|
|
181
|
+
...base,
|
|
182
|
+
key: `manifest.${f.key}.${lang}`,
|
|
183
|
+
lang,
|
|
184
|
+
required: f.required && lang === 'en',
|
|
185
|
+
});
|
|
186
|
+
if (value && typeof value === 'object') {
|
|
187
|
+
const langs = Object.keys(value);
|
|
188
|
+
if (!langs.includes('sv'))
|
|
189
|
+
langs.push('sv');
|
|
190
|
+
return langs.map(lang => localized(lang));
|
|
191
|
+
}
|
|
192
|
+
const plain = { ...base, key: `manifest.${f.key}` };
|
|
193
|
+
return LOCALIZABLE.has(f.key) ? [plain, localized('sv')] : [plain];
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const labelOf = (f) => t(f.label) + (f.lang ? ` (${f.lang})` : '');
|
|
135
197
|
const ENV_KEYS = new Set([
|
|
136
198
|
'domain',
|
|
137
199
|
'siteName',
|
|
@@ -170,6 +232,15 @@ function fromProject(project) {
|
|
|
170
232
|
signingUsername: dev.signingUsername ?? '',
|
|
171
233
|
certificateName: dev.certificateName ?? '',
|
|
172
234
|
signingPassword: '',
|
|
235
|
+
...Object.fromEntries(manifestFields(project.manifest).map(f => {
|
|
236
|
+
const value = project.manifest[f.manifestKey];
|
|
237
|
+
const text = f.lang && value && typeof value === 'object'
|
|
238
|
+
? value[f.lang]
|
|
239
|
+
: f.lang
|
|
240
|
+
? undefined
|
|
241
|
+
: value;
|
|
242
|
+
return [f.key, typeof text === 'string' ? text : ''];
|
|
243
|
+
})),
|
|
173
244
|
};
|
|
174
245
|
}
|
|
175
246
|
function storedSecret(project, key) {
|
|
@@ -190,8 +261,9 @@ const sameValue = (key, a, b) => JSON.stringify(a === '' || a === undefined ? IM
|
|
|
190
261
|
JSON.stringify(b === '' || b === undefined ? IMPLICIT_VALUES[key] : b);
|
|
191
262
|
/**
|
|
192
263
|
* App mode writes the app's complete file. Workspace mode never creates an
|
|
193
|
-
* app's .dev_properties.json: changes go to the root file, and
|
|
194
|
-
* to the app's package.json
|
|
264
|
+
* app's .dev_properties.json: changes go to the root file, and addon names (the
|
|
265
|
+
* base one and each environment's) to the app's package.json, since the root
|
|
266
|
+
* file is shared by every app. An app that already has its own file keeps it.
|
|
195
267
|
*/
|
|
196
268
|
function writeConfigFile(project, file) {
|
|
197
269
|
const { workspaceRoot } = project;
|
|
@@ -214,6 +286,40 @@ function writeConfigFile(project, file) {
|
|
|
214
286
|
packageJson['addonName'] = after[key] || undefined;
|
|
215
287
|
});
|
|
216
288
|
}
|
|
289
|
+
else if (key === 'environments') {
|
|
290
|
+
// `after` is this app's merged view. Only the environment being edited
|
|
291
|
+
// goes to the shared file, or the app's own overrides would reach
|
|
292
|
+
// every other app.
|
|
293
|
+
const environments = Object.entries((after[key] ?? {})).filter(([name]) => !project.environment || name === project.environment);
|
|
294
|
+
root[key] = {
|
|
295
|
+
...root[key],
|
|
296
|
+
...Object.fromEntries(environments.map(([name, { addonName: _addonName, ...shared }]) => [
|
|
297
|
+
name,
|
|
298
|
+
shared,
|
|
299
|
+
])),
|
|
300
|
+
};
|
|
301
|
+
updatePackageJson(project.root, packageJson => {
|
|
302
|
+
const svc = { ...packageJson['svc'] };
|
|
303
|
+
const own = new Map(Object.entries((svc['environments'] ?? {})));
|
|
304
|
+
for (const [name, { addonName }] of environments) {
|
|
305
|
+
const override = {
|
|
306
|
+
...own.get(name),
|
|
307
|
+
addonName: addonName || undefined,
|
|
308
|
+
};
|
|
309
|
+
if (Object.values(override).every(value => value === undefined)) {
|
|
310
|
+
own.delete(name);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
own.set(name, override);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
svc['environments'] =
|
|
317
|
+
own.size > 0 ? Object.fromEntries(own) : undefined;
|
|
318
|
+
packageJson['svc'] = Object.values(svc).some(value => value !== undefined)
|
|
319
|
+
? svc
|
|
320
|
+
: undefined;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
217
323
|
else {
|
|
218
324
|
root[key] = after[key];
|
|
219
325
|
}
|
|
@@ -223,22 +329,21 @@ function writeConfigFile(project, file) {
|
|
|
223
329
|
/** Apply the form to disk and the keychain. Exported for the test. */
|
|
224
330
|
export function saveConfig(project, values, edited) {
|
|
225
331
|
const method = values['authMethod'];
|
|
332
|
+
// Start from what is there: keys the form does not show, and the settings of
|
|
333
|
+
// the auth methods not in use, survive a save.
|
|
226
334
|
const next = {
|
|
335
|
+
...project.devProperties,
|
|
227
336
|
domain: values['domain'],
|
|
228
337
|
siteName: values['siteName'],
|
|
229
338
|
addonName: values['addonName'],
|
|
230
339
|
username: values['username'],
|
|
231
340
|
authMethod: method,
|
|
232
341
|
useHTTPForDevDeploy: values['useHTTPForDevDeploy'] === 'yes',
|
|
342
|
+
baseEnvironment: values['baseEnvironment']?.trim().toLowerCase() || undefined,
|
|
343
|
+
production: values['production'] === 'yes' ? true : undefined,
|
|
344
|
+
signingUsername: values['signingUsername'] || undefined,
|
|
345
|
+
certificateName: values['certificateName'] || undefined,
|
|
233
346
|
};
|
|
234
|
-
if (values['baseEnvironment'])
|
|
235
|
-
next.baseEnvironment = values['baseEnvironment'].trim().toLowerCase();
|
|
236
|
-
if (values['production'] === 'yes')
|
|
237
|
-
next.production = true;
|
|
238
|
-
if (values['signingUsername'])
|
|
239
|
-
next.signingUsername = values['signingUsername'];
|
|
240
|
-
if (values['certificateName'])
|
|
241
|
-
next.certificateName = values['certificateName'];
|
|
242
347
|
if (method === 'oauth2') {
|
|
243
348
|
const scopes = values['scopes'].split(/[\s,]+/).filter(Boolean);
|
|
244
349
|
next.oauth2 = {
|
|
@@ -251,8 +356,8 @@ export function saveConfig(project, values, edited) {
|
|
|
251
356
|
}),
|
|
252
357
|
};
|
|
253
358
|
}
|
|
254
|
-
else if (method === 'cookie'
|
|
255
|
-
next.sessionLoginUrl = values['sessionLoginUrl'];
|
|
359
|
+
else if (method === 'cookie') {
|
|
360
|
+
next.sessionLoginUrl = values['sessionLoginUrl'] || undefined;
|
|
256
361
|
}
|
|
257
362
|
const env = project.environment;
|
|
258
363
|
if (env && env !== baseEnvironment(project.base) && project.base) {
|
|
@@ -306,6 +411,11 @@ export function visibleFields(method, workspace = false, envMode = false) {
|
|
|
306
411
|
}
|
|
307
412
|
export function ConfigForm({ project, active, width, height, pickAddon, onSaved, onEditingChange, }) {
|
|
308
413
|
const [values, setValues] = useState(() => fromProject(project));
|
|
414
|
+
// What the form holds when an async lookup lands, not when it started.
|
|
415
|
+
const latest = useRef({ values, project });
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
latest.current = { values, project };
|
|
418
|
+
});
|
|
309
419
|
const [cursor, setCursor] = useState(0);
|
|
310
420
|
const [editing, setEditing] = useState(false);
|
|
311
421
|
const [draft, setDraft] = useState('');
|
|
@@ -325,28 +435,42 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
325
435
|
const envMode = Boolean(project.environment &&
|
|
326
436
|
project.environment !==
|
|
327
437
|
baseEnvironment(project.base ?? project.devProperties));
|
|
328
|
-
const fields =
|
|
438
|
+
const fields = [
|
|
439
|
+
...visibleFields(method, project.workspace, envMode),
|
|
440
|
+
...manifestFields(project.manifest),
|
|
441
|
+
];
|
|
329
442
|
const current = fields[Math.min(cursor, fields.length - 1)];
|
|
330
443
|
const inherited = readInheritedDevProperties(project.root);
|
|
331
444
|
const ancestors = readAncestorDevProperties(project.root);
|
|
445
|
+
const globalSigning = getGlobalSigning();
|
|
332
446
|
const changes = getPackageJsonSyncChanges(project.root);
|
|
333
447
|
const packageJsonExists = hasPackageJson(project.root);
|
|
334
448
|
// Write one field to disk (and the keychain for secrets) right away.
|
|
335
|
-
const commit = (key, value, label = current
|
|
449
|
+
const commit = (key, value, label = labelOf(current)) => {
|
|
336
450
|
const clean = key === 'domain' ? normalizeDomain(value) : value;
|
|
451
|
+
const field = fields.find(f => f.key === key);
|
|
452
|
+
if (field?.manifestKey && field.required && !clean) {
|
|
453
|
+
setNote(t('Not saved: {label} is required.', { label }));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
337
456
|
const next = { ...values, [key]: clean };
|
|
338
457
|
setValues(next);
|
|
339
458
|
try {
|
|
340
|
-
|
|
459
|
+
if (field?.manifestKey) {
|
|
460
|
+
writeManifestField(project.manifestPath, field.manifestKey, clean, field.lang);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
saveConfig(project, next, new Set([key]));
|
|
464
|
+
}
|
|
341
465
|
}
|
|
342
466
|
catch (error) {
|
|
343
467
|
setNote(t('Not saved: {error}', { error: errorText(error) }));
|
|
344
468
|
return;
|
|
345
469
|
}
|
|
346
470
|
setNote(clean === value
|
|
347
|
-
? t('Saved {label}.', { label
|
|
471
|
+
? t('Saved {label}.', { label })
|
|
348
472
|
: t('Saved {label} as {value} — a domain is a host only.', {
|
|
349
|
-
label
|
|
473
|
+
label,
|
|
350
474
|
value: clean,
|
|
351
475
|
}));
|
|
352
476
|
onSaved();
|
|
@@ -363,14 +487,15 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
363
487
|
if (cancelled)
|
|
364
488
|
return;
|
|
365
489
|
if (found) {
|
|
490
|
+
const now = latest.current.values;
|
|
366
491
|
const next = {
|
|
367
|
-
...
|
|
368
|
-
authorizationEndpoint:
|
|
369
|
-
tokenEndpoint:
|
|
492
|
+
...now,
|
|
493
|
+
authorizationEndpoint: now['authorizationEndpoint'] || found.authorizationEndpoint,
|
|
494
|
+
tokenEndpoint: now['tokenEndpoint'] || found.tokenEndpoint,
|
|
370
495
|
};
|
|
371
496
|
setValues(next);
|
|
372
497
|
try {
|
|
373
|
-
saveConfig(project, next, new Set());
|
|
498
|
+
saveConfig(latest.current.project, next, new Set());
|
|
374
499
|
}
|
|
375
500
|
catch (error) {
|
|
376
501
|
setNote(t('Not saved: {error}', { error: errorText(error) }));
|
|
@@ -394,7 +519,7 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
394
519
|
void pickAddon().then(name => {
|
|
395
520
|
if (name) {
|
|
396
521
|
setEditing(false);
|
|
397
|
-
commit('addonName', name, 'Addon name');
|
|
522
|
+
commit('addonName', name, t('Addon name'));
|
|
398
523
|
}
|
|
399
524
|
});
|
|
400
525
|
};
|
|
@@ -461,13 +586,21 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
461
586
|
setEditing(true);
|
|
462
587
|
}
|
|
463
588
|
}, { isActive: active });
|
|
589
|
+
// An environment's addon name differs from the app's name on purpose.
|
|
590
|
+
const drift = envMode || !getSettings().addonNameDriftWarning
|
|
591
|
+
? undefined
|
|
592
|
+
: addonNameDrift(values['addonName'], project.manifest);
|
|
464
593
|
const source = (f) => {
|
|
465
594
|
const required = f.required === true || f.required === method;
|
|
466
595
|
if (required && !(values[f.key] ?? ''))
|
|
467
596
|
return { text: t('✗ required'), color: 'red' };
|
|
597
|
+
if (f.key === 'addonName' && drift)
|
|
598
|
+
return { text: t('≠ manifest'), color: 'yellow' };
|
|
468
599
|
if (f.kind === 'secret') {
|
|
469
600
|
return { text: storedSecret(project, f.key) ? t('keychain') : '—' };
|
|
470
601
|
}
|
|
602
|
+
if (f.manifestKey)
|
|
603
|
+
return { text: (values[f.key] ?? '') ? 'manifest.json' : '' };
|
|
471
604
|
const value = f.kind === 'bool' ? values[f.key] === 'yes' : values[f.key];
|
|
472
605
|
if (envMode && ENV_KEYS.has(f.key)) {
|
|
473
606
|
const override = project.base?.environments?.[project.environment];
|
|
@@ -502,17 +635,22 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
502
635
|
: 'package.json',
|
|
503
636
|
};
|
|
504
637
|
}
|
|
638
|
+
if (value && globalSigning[f.key] === value)
|
|
639
|
+
return { text: t('global') };
|
|
505
640
|
return { text: (values[f.key] ?? '') ? t('local') : '' };
|
|
506
641
|
};
|
|
507
642
|
// label column (24) + source column + paddings; never below 20.
|
|
508
643
|
const valueWidth = Math.max(20, width - 24 - SOURCE_WIDTH - 2);
|
|
509
644
|
const rows = [];
|
|
645
|
+
let focusRow = 0;
|
|
510
646
|
let lastSection;
|
|
511
647
|
for (const f of fields) {
|
|
512
648
|
if (f.section && f.section !== lastSection) {
|
|
513
|
-
rows.push(_jsx(Box, {
|
|
649
|
+
rows.push(_jsx(Box, { height: 1, flexShrink: 0 }, `g-${f.section}`), _jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: t(f.section) }) }, `s-${f.section}`));
|
|
514
650
|
lastSection = f.section;
|
|
515
651
|
}
|
|
652
|
+
if (f === current)
|
|
653
|
+
focusRow = rows.length;
|
|
516
654
|
const focused = active && f === current;
|
|
517
655
|
const typing = focused && editing;
|
|
518
656
|
// A value being typed into, scrolled so the caret stays in view and drawn
|
|
@@ -544,9 +682,13 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
544
682
|
display = typing ? (withCaret(draft)) : value ? (_jsx(Text, { children: value })) : (_jsx(Text, { dimColor: true, children: f.hint ? t(f.hint) : '—' }));
|
|
545
683
|
}
|
|
546
684
|
const src = source(f);
|
|
547
|
-
rows.push(_jsxs(Box, { height: 1, flexShrink: 0, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') +
|
|
685
|
+
rows.push(_jsxs(Box, { height: 1, flexShrink: 0, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + labelOf(f).padEnd(22) }), _jsx(Box, { width: valueWidth, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", children: display }) }), _jsx(Box, { width: SOURCE_WIDTH, flexShrink: 0, children: _jsx(Text, { dimColor: !src.color, color: src.color, wrap: "truncate", children: focused && f.hint && f.key === 'addonName' ? t(f.hint) : src.text }) })] }, f.key));
|
|
548
686
|
}
|
|
549
|
-
|
|
687
|
+
// ponytail: the help panel is assumed to wrap to three lines; measure it if
|
|
688
|
+
// a narrow pane ever clips the note.
|
|
689
|
+
const room = Math.max(3, height - 9 - changes.length - (project.workspace ? 2 : 0));
|
|
690
|
+
const firstRow = Math.max(0, Math.min(focusRow - Math.floor(room / 2), rows.length - room));
|
|
691
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", height: height, children: [_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [(' ' + t('FIELD')).padEnd(24), t('VALUE').padEnd(valueWidth), t('SOURCE')] }) }), rows.slice(firstRow, firstRow + room), project.workspace && (_jsx(Box, { marginTop: 1, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: t("Shared by every app below {root}. An app's own value wins.", {
|
|
550
692
|
root: project.root,
|
|
551
693
|
}) }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 0, children: [_jsxs(Text, { bold: true, dimColor: true, children: [t('PACKAGE.JSON SYNC'), ' ', _jsx(Text, { color: changes.length > 0 || !packageJsonExists ? 'yellow' : 'green', children: !packageJsonExists
|
|
552
694
|
? t('no workspace package.json · y to set up')
|
|
@@ -554,5 +696,8 @@ export function ConfigForm({ project, active, width, height, pickAddon, onSaved,
|
|
|
554
696
|
? t('in sync')
|
|
555
697
|
: changes.length === 1
|
|
556
698
|
? t('1 diff · y to apply')
|
|
557
|
-
: t('{n} diffs · y to apply', { n: changes.length }) })] }),
|
|
699
|
+
: t('{n} diffs · y to apply', { n: changes.length }) })] }), drift && (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: "yellow", children: "\u2260 " }), t('addon name {addon} · manifest name {names}', {
|
|
700
|
+
addon: values['addonName'] ?? '',
|
|
701
|
+
names: drift.join(' / '),
|
|
702
|
+
})] })), changes.map(c => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: c.from === undefined ? 'green' : 'yellow', children: c.from === undefined ? '+ ' : '~ ' }), c.key, ": ", c.from !== undefined && _jsxs(Text, { dimColor: true, children: [c.from, " \u2192 "] }), c.to] }, c.key)))] }), _jsx(Box, { flexGrow: 1 }), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderDimColor: true, borderLeft: false, borderRight: false, borderBottom: false, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { bold: true, color: ACCENT, children: labelOf(current) }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t(current.help)] })] }) }), _jsx(Text, { color: "yellow", children: note })] }));
|
|
558
703
|
}
|
package/dist/shell/Frame.js
CHANGED
|
@@ -11,7 +11,10 @@ export function navWidth(columns) {
|
|
|
11
11
|
return Math.min(48, Math.max(32, Math.floor(columns / 4)));
|
|
12
12
|
}
|
|
13
13
|
export function TopBar({ context, domain, auth, version, environment, }) {
|
|
14
|
-
|
|
14
|
+
// Narrow terminals squeeze the domain first, then the context, then the
|
|
15
|
+
// login. `svc` and the environment badge never give way: the badge is the
|
|
16
|
+
// one thing that says a deploy goes to production.
|
|
17
|
+
return (_jsxs(Box, { paddingX: 1, height: 1, overflow: "hidden", children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { bold: true, color: ACCENT, children: "svc" }), environment && (_jsxs(Text, { children: [' ', _jsx(Text, { backgroundColor: environment.color, color: "black", bold: true, children: ` ${environment.name.toUpperCase()} ` })] }))] }), _jsx(Box, { flexShrink: 2, flexGrow: 1, marginRight: 2, minWidth: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', context] }) }), domain && (_jsx(Box, { flexShrink: 100, minWidth: 0, marginRight: 1, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: domain }) })), _jsx(Box, { flexShrink: 1, minWidth: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: auth.ready ? 'green' : 'yellow', children: auth.ready ? '●' : '○' }), _jsxs(Text, { children: [" ", auth.label] })] }) }), _jsx(Box, { flexShrink: 0, children: _jsxs(Text, { dimColor: true, children: [" v", version] }) })] }));
|
|
15
18
|
}
|
|
16
19
|
const TYPE_GLYPH = {
|
|
17
20
|
web: 'web',
|
package/dist/shell/Help.js
CHANGED
|
@@ -66,10 +66,11 @@ export function HelpPanel({ here, where, height, onClose, }) {
|
|
|
66
66
|
const inner = useRef(null);
|
|
67
67
|
const [top, setTop] = useState(0);
|
|
68
68
|
const [total, setTotal] = useState(0);
|
|
69
|
+
// The text depends on the pane and the language, both fixed while this is open.
|
|
69
70
|
useEffect(() => {
|
|
70
71
|
if (inner.current)
|
|
71
72
|
setTotal(measureElement(inner.current).height);
|
|
72
|
-
});
|
|
73
|
+
}, [here, where, height]);
|
|
73
74
|
const visible = Math.max(1, height - 1);
|
|
74
75
|
const max = Math.max(0, total - visible);
|
|
75
76
|
useInput((input, key) => {
|
package/dist/shell/Settings.js
CHANGED
|
@@ -1,36 +1,73 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { getSettings, setSettings, settingsFile } from '../utils/config.js';
|
|
4
|
+
import { configProblem, getGlobalSigning, getSettings, setGlobalSigning, setSettings, settingsFile, } from '../utils/config.js';
|
|
5
5
|
import { LANGUAGES, LANGUAGE_NAMES, setLanguage, t, } from '../utils/i18n.js';
|
|
6
6
|
import { ACCENT } from './Frame.js';
|
|
7
7
|
const ROWS = [
|
|
8
8
|
{
|
|
9
9
|
key: 'language',
|
|
10
10
|
label: 'Language',
|
|
11
|
+
help: 'The language svc itself speaks.',
|
|
11
12
|
options: LANGUAGES,
|
|
12
13
|
display: value => LANGUAGE_NAMES[value],
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
16
|
key: 'introAnimation',
|
|
16
17
|
label: 'Intro animation',
|
|
18
|
+
help: 'Play the logo animation when the shell starts.',
|
|
17
19
|
options: ['on', 'off'],
|
|
18
20
|
display: value => t(value),
|
|
19
21
|
},
|
|
22
|
+
{
|
|
23
|
+
key: 'updateCheck',
|
|
24
|
+
label: 'Update check',
|
|
25
|
+
help: 'Look for a newer svc on npm at start.',
|
|
26
|
+
options: ['on', 'off'],
|
|
27
|
+
display: value => t(value),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'addonNameDriftWarning',
|
|
31
|
+
label: 'Addon name warning',
|
|
32
|
+
help: 'Mark the addon name with ≠ manifest when it is none of the names in manifest.json.',
|
|
33
|
+
options: ['on', 'off'],
|
|
34
|
+
display: value => t(value),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'signingUsername',
|
|
38
|
+
label: 'Default signing user',
|
|
39
|
+
help: 'The developer.sitevision.se account that signs in every project without a signing user of its own.',
|
|
40
|
+
options: [],
|
|
41
|
+
display: value => value,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: 'certificateName',
|
|
45
|
+
label: 'Default certificate',
|
|
46
|
+
help: 'Which certificate signs when the account has several. Empty = the account default.',
|
|
47
|
+
options: [],
|
|
48
|
+
display: value => value,
|
|
49
|
+
},
|
|
20
50
|
];
|
|
21
51
|
/** Global preferences, saved per field like the Config tab. */
|
|
22
52
|
export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
23
53
|
const [values, setValues] = useState(() => {
|
|
24
54
|
const s = getSettings();
|
|
55
|
+
const signing = getGlobalSigning();
|
|
25
56
|
return {
|
|
26
57
|
language: s.language,
|
|
27
58
|
introAnimation: s.introAnimation ? 'on' : 'off',
|
|
59
|
+
updateCheck: s.updateCheck ? 'on' : 'off',
|
|
60
|
+
addonNameDriftWarning: s.addonNameDriftWarning ? 'on' : 'off',
|
|
61
|
+
signingUsername: signing.signingUsername ?? '',
|
|
62
|
+
certificateName: signing.certificateName ?? '',
|
|
28
63
|
};
|
|
29
64
|
});
|
|
30
65
|
const [cursor, setCursor] = useState(0);
|
|
31
66
|
const [editing, setEditing] = useState(false);
|
|
32
67
|
const [draft, setDraft] = useState('');
|
|
33
|
-
const [note, setNote] = useState(
|
|
68
|
+
const [note, setNote] = useState(() => configProblem()
|
|
69
|
+
? t('The settings file does not parse and is ignored until it is fixed.')
|
|
70
|
+
: '');
|
|
34
71
|
const rowCount = ROWS.length + (onOpenWorkspace ? 1 : 0);
|
|
35
72
|
const onWorkspaceRow = cursor === ROWS.length;
|
|
36
73
|
const current = ROWS[Math.min(cursor, ROWS.length - 1)];
|
|
@@ -42,8 +79,16 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
42
79
|
setSettings({
|
|
43
80
|
language: next.language,
|
|
44
81
|
introAnimation: next.introAnimation === 'on',
|
|
82
|
+
updateCheck: next.updateCheck === 'on',
|
|
83
|
+
addonNameDriftWarning: next.addonNameDriftWarning === 'on',
|
|
84
|
+
});
|
|
85
|
+
setGlobalSigning({
|
|
86
|
+
signingUsername: next.signingUsername.trim() || undefined,
|
|
87
|
+
certificateName: next.certificateName.trim() || undefined,
|
|
45
88
|
});
|
|
46
|
-
setNote(
|
|
89
|
+
setNote(configProblem()
|
|
90
|
+
? t('Not saved: the settings file does not parse.')
|
|
91
|
+
: t('Saved.'));
|
|
47
92
|
onChanged();
|
|
48
93
|
};
|
|
49
94
|
useInput((input, key) => {
|
|
@@ -56,6 +101,12 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
56
101
|
if (draft !== values[current.key])
|
|
57
102
|
commit(draft);
|
|
58
103
|
}
|
|
104
|
+
else if (current.options.length === 0) {
|
|
105
|
+
if (key.backspace || key.delete)
|
|
106
|
+
setDraft(d => d.slice(0, -1));
|
|
107
|
+
else if (input && !key.ctrl && !key.meta)
|
|
108
|
+
setDraft(d => d + input);
|
|
109
|
+
}
|
|
59
110
|
else {
|
|
60
111
|
const step = key.leftArrow || key.upArrow
|
|
61
112
|
? -1
|
|
@@ -89,8 +140,10 @@ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
|
|
|
89
140
|
const focused = i === cursor && !onWorkspaceRow;
|
|
90
141
|
const typing = focused && editing;
|
|
91
142
|
const chosen = typing ? draft : values[row.key];
|
|
92
|
-
return (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + t(row.label).padEnd(
|
|
93
|
-
}), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(
|
|
94
|
-
?
|
|
143
|
+
return (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + t(row.label).padEnd(24) }), row.options.length === 0 && (_jsxs(Text, { dimColor: !typing && !chosen, children: [chosen || (typing ? '' : t('not set')), typing && _jsx(Text, { inverse: true, children: " " })] })), _jsx(Text, { children: row.options.map((option, j) => (_jsxs(Text, { children: [_jsx(Text, { bold: option === chosen, color: option === chosen ? ACCENT : undefined, inverse: typing && option === chosen, dimColor: option !== chosen, children: row.display(option) }), j < row.options.length - 1 && _jsx(Text, { dimColor: true, children: " \u00B7 " })] }, option))) })] }, row.key));
|
|
144
|
+
}), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(24) }), _jsx(Text, { dimColor: true, children: t('shared auth and site config for every app') })] })), _jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: onWorkspaceRow ? ' ' : t(current.help) }) }), _jsx(Text, { color: "yellow", children: note }), _jsx(Text, { dimColor: true, children: editing
|
|
145
|
+
? current.options.length === 0
|
|
146
|
+
? t('Enter confirm · Esc cancel')
|
|
147
|
+
: t('←→ choose · Enter confirm · Esc cancel')
|
|
95
148
|
: t('↑↓ setting · Enter edit · Esc close') })] }));
|
|
96
149
|
}
|