sitevision-cli 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +1 -1
- package/dist/app.js +59 -8
- package/dist/cli.js +96 -39
- package/dist/commands/build.js +1 -1
- package/dist/commands/deploy.d.ts +2 -2
- package/dist/commands/deploy.js +135 -25
- package/dist/commands/dev.d.ts +8 -10
- package/dist/commands/dev.js +77 -366
- package/dist/commands/info.js +2 -2
- package/dist/commands/watch.js +5 -23
- package/dist/components/AnimatedLogo.js +8 -2
- package/dist/components/AuthLoginScreen.d.ts +21 -0
- package/dist/components/AuthLoginScreen.js +90 -0
- package/dist/components/DevPropertiesForm.d.ts +2 -1
- package/dist/components/DevPropertiesForm.js +198 -33
- package/dist/components/InfoScreen.js +2 -2
- package/dist/components/MainMenu.js +7 -2
- package/dist/components/PasswordInput.js +2 -1
- package/dist/components/SetupFlow.d.ts +2 -1
- package/dist/components/SetupFlow.js +100 -11
- package/dist/shell/AddonPicker.d.ts +14 -0
- package/dist/shell/AddonPicker.js +54 -0
- package/dist/shell/CommandPalette.d.ts +8 -0
- package/dist/shell/CommandPalette.js +63 -0
- package/dist/shell/ConfigForm.d.ts +36 -0
- package/dist/shell/ConfigForm.js +558 -0
- package/dist/shell/Frame.d.ts +59 -0
- package/dist/shell/Frame.js +134 -0
- package/dist/shell/Settings.d.ts +6 -0
- package/dist/shell/Settings.js +96 -0
- package/dist/shell/Shell.d.ts +9 -0
- package/dist/shell/Shell.js +586 -0
- package/dist/shell/Tabs.d.ts +36 -0
- package/dist/shell/Tabs.js +90 -0
- package/dist/shell/actions.d.ts +45 -0
- package/dist/shell/actions.js +0 -0
- package/dist/types/index.d.ts +44 -5
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/environments.d.ts +20 -0
- package/dist/utils/environments.js +74 -0
- package/dist/utils/i18n.d.ts +12 -0
- package/dist/utils/i18n.js +279 -0
- package/dist/utils/jsonc.d.ts +19 -0
- package/dist/utils/jsonc.js +74 -0
- package/dist/utils/keychain.d.ts +9 -0
- package/dist/utils/keychain.js +54 -0
- package/dist/utils/oauth2-auth.d.ts +64 -0
- package/dist/utils/oauth2-auth.js +242 -0
- package/dist/utils/password-prompt.d.ts +5 -0
- package/dist/utils/password-prompt.js +28 -0
- package/dist/utils/project-detection.d.ts +105 -6
- package/dist/utils/project-detection.js +411 -54
- package/dist/utils/session-cookie-auth.d.ts +35 -0
- package/dist/utils/session-cookie-auth.js +99 -0
- package/dist/utils/sitevision-api.d.ts +64 -5
- package/dist/utils/sitevision-api.js +195 -33
- package/dist/utils/tasks.d.ts +48 -0
- package/dist/utils/tasks.js +371 -0
- package/dist/utils/workspace.d.ts +17 -0
- package/dist/utils/workspace.js +67 -0
- package/package.json +3 -1
- package/readme.md +102 -121
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { appTypeOf, getPackageJsonSyncChanges, readAncestorDevProperties, localizedText, } from '../utils/project-detection.js';
|
|
5
|
+
import { checkSitevisionScriptsCompatibility } from '../utils/sitevision-scripts-runner.js';
|
|
6
|
+
import { ACCENT, elapsed } from './Frame.js';
|
|
7
|
+
import { t } from '../utils/i18n.js';
|
|
8
|
+
export const TABS = [
|
|
9
|
+
{ id: 'overview', label: 'Overview', short: 'Ovw' },
|
|
10
|
+
{ id: 'config', label: 'Config', short: 'Cfg' },
|
|
11
|
+
{ id: 'versions', label: 'Versions', short: 'Ver' },
|
|
12
|
+
{ id: 'log', label: 'Log', short: 'Log' },
|
|
13
|
+
];
|
|
14
|
+
export function TabBar({ tab, narrow, focused, }) {
|
|
15
|
+
return (_jsx(Box, { paddingX: 1, children: TABS.map((entry, i) => (_jsxs(Text, { children: [_jsxs(Text, { bold: entry.id === tab, color: entry.id === tab ? ACCENT : undefined, dimColor: entry.id !== tab, underline: entry.id === tab && focused, children: [i + 1, " ", t(narrow ? entry.short : entry.label)] }), ' '.repeat(3)] }, entry.id))) }));
|
|
16
|
+
}
|
|
17
|
+
function Row({ label, value, dim, }) {
|
|
18
|
+
return (_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { dimColor: true, children: label.padEnd(14) }), value, dim && _jsxs(Text, { dimColor: true, children: [" ", dim] })] }) }));
|
|
19
|
+
}
|
|
20
|
+
const LEVEL_COLOR = {
|
|
21
|
+
info: undefined,
|
|
22
|
+
ok: 'green',
|
|
23
|
+
warn: 'yellow',
|
|
24
|
+
error: 'red',
|
|
25
|
+
};
|
|
26
|
+
const STATUS_GLYPH = {
|
|
27
|
+
running: _jsx(Spinner, { type: "dots" }),
|
|
28
|
+
success: _jsx(Text, { color: "green", children: "\u2713" }),
|
|
29
|
+
error: _jsx(Text, { color: "red", children: "\u2717" }),
|
|
30
|
+
stopped: _jsx(Text, { dimColor: true, children: "\u25A0" }),
|
|
31
|
+
};
|
|
32
|
+
function time(ms, seconds = false) {
|
|
33
|
+
const d = new Date(ms);
|
|
34
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
35
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
36
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
37
|
+
return seconds ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
|
|
38
|
+
}
|
|
39
|
+
export function Overview({ project, tasks, height, }) {
|
|
40
|
+
const dev = project.devProperties;
|
|
41
|
+
const inherited = new Set(project.inheritedKeys);
|
|
42
|
+
const ancestors = readAncestorDevProperties(project.root);
|
|
43
|
+
const src = (key) => inherited.has(key)
|
|
44
|
+
? Object.hasOwn(ancestors, key)
|
|
45
|
+
? t('↑ root')
|
|
46
|
+
: 'package.json'
|
|
47
|
+
: undefined;
|
|
48
|
+
const notSet = t('not set');
|
|
49
|
+
const sync = getPackageJsonSyncChanges(project.root).length;
|
|
50
|
+
const scripts = checkSitevisionScriptsCompatibility(project.root);
|
|
51
|
+
const recent = tasks
|
|
52
|
+
.filter(task => task.appRoot === project.root && task.status !== 'running')
|
|
53
|
+
.slice(-Math.max(1, height - 18))
|
|
54
|
+
.toReversed();
|
|
55
|
+
const status = (ok, okText, badText, warn = false) => (_jsxs(Text, { children: [_jsx(Text, { color: ok ? 'green' : warn ? 'yellow' : 'red', children: ok ? '✓' : warn ? '~' : '✗' }), ' ', ok ? okText : badText] }));
|
|
56
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { bold: true, wrap: "truncate", children: [localizedText(project.manifest.name) || project.manifest.id, ' ', _jsxs(Text, { dimColor: true, children: [project.manifest.type, project.manifest.bundled ? t(' · bundled') : ''] })] }) }), _jsx(Row, { label: t('id'), value: project.manifest.id }), _jsx(Row, { label: t('version'), value: project.manifest.version }), _jsx(Row, { label: t('type'), value: `${project.manifest.type} (${appTypeOf(project.manifest) ?? '?'})` }), _jsx(Row, { label: t('addon'), value: dev?.addonName ?? notSet, dim: src('addonName') }), _jsx(Row, { label: t('site'), value: dev?.siteName ?? notSet, dim: src('siteName') }), _jsx(Row, { label: t('environment'), value: dev?.environmentName ?? 'dev', dim: dev?.productionEnvironment ? t('production') : undefined }), _jsx(Row, { label: t('domain'), value: dev?.domain ?? notSet, dim: src('domain') }), _jsx(Row, { label: t('auth'), value: dev?.authMethod ?? (dev ? 'basic' : notSet), dim: src('authMethod') }), _jsx(Row, { label: t('signing user'), value: dev?.signingUsername ?? notSet, dim: src('signingUsername') }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 0, children: [_jsxs(Text, { dimColor: true, children: [t('deps').padEnd(10), status(project.hasNodeModules, 'node_modules', t('missing · i to install'))] }), _jsxs(Text, { dimColor: true, children: [t('config').padEnd(10), status(Boolean(dev), t('dev properties'), t('missing · e to edit'))] }), _jsxs(Text, { dimColor: true, children: [t('sync').padEnd(10), status(sync === 0, 'package.json', sync === 1
|
|
57
|
+
? t('1 diff · y to apply')
|
|
58
|
+
: t('{n} diffs · y to apply', { n: sync }), true)] }), _jsxs(Text, { dimColor: true, children: [t('signing').padEnd(10), status(project.hasSigningProperties, dev?.signingUsername ?? '', t('missing · / set up signing'))] }), _jsxs(Text, { dimColor: true, children: [t('scripts').padEnd(10), status(scripts.status === 'ok', scripts.installed ?? '', scripts.installed
|
|
59
|
+
? `${scripts.installed} · ${scripts.status}`
|
|
60
|
+
: t('not installed · i to install'), scripts.status !== 'not-installed')] }), project.hasLegacyPassword && (_jsxs(Text, { color: "yellow", children: [' '.repeat(10), t('⚠ plaintext password in .dev_properties.json · / migrate')] }))] }), recent.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: t('RECENT') }), recent.map(task => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { dimColor: true, children: [time(task.endedAt ?? task.startedAt), " "] }), STATUS_GLYPH[task.status], " ", task.label, ' ', _jsx(Text, { dimColor: true, children: task.error ?? elapsed(task) })] }, task.id)))] }))] }));
|
|
61
|
+
}
|
|
62
|
+
export function Versions({ project, state, selected, }) {
|
|
63
|
+
if (!project.devProperties) {
|
|
64
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellow", children: t('⚠ Configure dev properties first (e).') }) }));
|
|
65
|
+
}
|
|
66
|
+
if (!state) {
|
|
67
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: t('Press R to fetch versions from {domain}.', {
|
|
68
|
+
domain: project.devProperties.domain,
|
|
69
|
+
}) }) }));
|
|
70
|
+
}
|
|
71
|
+
const list = state.executables ?? [];
|
|
72
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [_jsxs(Text, { dimColor: true, children: [t('APP IDENTIFIER').padEnd(30), t('VERSION').padEnd(12), t('ACTIVE'), state.loading && (_jsxs(Text, { color: ACCENT, children: [' '.repeat(3), _jsx(Spinner, { type: "dots" })] }))] }), state.error && _jsx(Text, { color: "red", children: state.error }), list.map((e, i) => (_jsxs(Text, { backgroundColor: i === selected ? ACCENT : undefined, color: i === selected ? 'black' : undefined, wrap: "truncate", children: [e.appIdentifier.padEnd(30).slice(0, 30), e.appVersion.padEnd(12), e.active ? '●' : '○', i === selected ? ` ${e.id}` : ''] }, e.id))), !state.loading && !state.error && list.length === 0 && (_jsx(Text, { dimColor: true, children: t('No versions uploaded to {addon}.', {
|
|
73
|
+
addon: project.devProperties.addonName,
|
|
74
|
+
}) })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [list.length === 1
|
|
75
|
+
? t('1 version · a activate selected · R refresh')
|
|
76
|
+
: t('{n} versions · a activate selected · R refresh', {
|
|
77
|
+
n: list.length,
|
|
78
|
+
}), state.fetchedAt
|
|
79
|
+
? t(' · fetched {time}', { time: time(state.fetchedAt) })
|
|
80
|
+
: ''] }) })] }));
|
|
81
|
+
}
|
|
82
|
+
export function Log({ task, height, scroll, wrap, }) {
|
|
83
|
+
if (!task) {
|
|
84
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: t('No task yet. d dev · w watch · b build · s sign · p deploy') }) }));
|
|
85
|
+
}
|
|
86
|
+
const visible = Math.max(1, height - 2);
|
|
87
|
+
const end = Math.max(0, task.lines.length - scroll);
|
|
88
|
+
const lines = task.lines.slice(Math.max(0, end - visible), end);
|
|
89
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: [_jsxs(Text, { children: [STATUS_GLYPH[task.status], " ", task.label, " ", task.appName, ' ', _jsxs(Text, { dimColor: true, children: [task.phase, " \u00B7 ", elapsed(task), scroll > 0 ? ` · ↑${scroll}` : t(' · following')] })] }), lines.map((line, i) => (_jsxs(Text, { wrap: wrap ? 'wrap' : 'truncate', color: LEVEL_COLOR[line.level], children: [_jsxs(Text, { dimColor: true, children: [time(line.time, true), " ", line.tag.padEnd(3)] }), ' ', line.text] }, i)))] }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ProjectInfo, DeployConfig } from '../types/index.js';
|
|
2
|
+
export type Tab = 'overview' | 'config' | 'versions' | 'log';
|
|
3
|
+
export type Credential = {
|
|
4
|
+
accessToken?: string;
|
|
5
|
+
sessionCookie?: string;
|
|
6
|
+
};
|
|
7
|
+
export interface ActionContext {
|
|
8
|
+
project: ProjectInfo;
|
|
9
|
+
reload: () => void;
|
|
10
|
+
askPassword: (label: string, rememberLabel?: string) => Promise<{
|
|
11
|
+
password: string;
|
|
12
|
+
remember: boolean;
|
|
13
|
+
} | null>;
|
|
14
|
+
login: (method: 'oauth2' | 'cookie') => Promise<Credential | null>;
|
|
15
|
+
confirm: (message: string) => Promise<boolean>;
|
|
16
|
+
setTab: (tab: Tab) => void;
|
|
17
|
+
openWorkspaceSettings?: () => void;
|
|
18
|
+
openSettings: () => void;
|
|
19
|
+
environment: string;
|
|
20
|
+
isProduction: boolean;
|
|
21
|
+
cycleEnvironment: () => void;
|
|
22
|
+
addEnvironment: () => Promise<void>;
|
|
23
|
+
notify: (text: string, level?: 'info' | 'ok' | 'warn' | 'error') => void;
|
|
24
|
+
quit: () => void;
|
|
25
|
+
}
|
|
26
|
+
export interface Action {
|
|
27
|
+
id: string;
|
|
28
|
+
key?: string;
|
|
29
|
+
group: 'app' | 'setup' | 'auth';
|
|
30
|
+
label: string;
|
|
31
|
+
detail?: (project: ProjectInfo) => string | undefined;
|
|
32
|
+
enabled?: (project: ProjectInfo) => boolean;
|
|
33
|
+
run: (ctx: ActionContext) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function forgetSession(project: ProjectInfo): void;
|
|
36
|
+
/** Top-bar auth state without prompting for anything. */
|
|
37
|
+
export declare function authState(project: ProjectInfo): {
|
|
38
|
+
ready: boolean;
|
|
39
|
+
label: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function resolveDeployConfig(ctx: ActionContext, fresh?: boolean): Promise<DeployConfig | null>;
|
|
42
|
+
export declare const actions: Action[];
|
|
43
|
+
export declare function actionForKey(key: string): Action | undefined;
|
|
44
|
+
/** Subsequence match, case-insensitive: "dpp" matches "Deploy to production". */
|
|
45
|
+
export declare function fuzzyMatch(query: string, text: string): boolean;
|
|
Binary file
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,21 +6,27 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* App types supported by Sitevision
|
|
8
8
|
*/
|
|
9
|
-
export type AppType = 'WebApp' | 'Widget' | 'RESTApp';
|
|
9
|
+
export type AppType = 'WebApp' | 'Widget' | 'RESTApp' | 'MCPServer';
|
|
10
10
|
/**
|
|
11
11
|
* Simplified app type for internal use
|
|
12
12
|
*/
|
|
13
|
-
export type SimpleAppType = 'web' | 'widget' | 'rest';
|
|
13
|
+
export type SimpleAppType = 'web' | 'widget' | 'rest' | 'mcp';
|
|
14
|
+
/**
|
|
15
|
+
* A manifest text field that may be a plain string or a localized object keyed
|
|
16
|
+
* by language code, e.g. `{sv: 'Namn', en: 'Name'}`. Sitevision allows either
|
|
17
|
+
* form for human-facing fields like `name` and `description`.
|
|
18
|
+
*/
|
|
19
|
+
export type LocalizedString = string | Record<string, string>;
|
|
14
20
|
/**
|
|
15
21
|
* Sitevision app manifest (manifest.json)
|
|
16
22
|
*/
|
|
17
23
|
export interface SitevisionManifest {
|
|
18
24
|
id: string;
|
|
19
|
-
name:
|
|
25
|
+
name: LocalizedString;
|
|
20
26
|
version: string;
|
|
21
27
|
type: AppType;
|
|
22
28
|
bundled?: boolean;
|
|
23
|
-
description?:
|
|
29
|
+
description?: LocalizedString;
|
|
24
30
|
author?: string;
|
|
25
31
|
helpUrl?: string;
|
|
26
32
|
license?: string;
|
|
@@ -42,6 +48,33 @@ export interface DevProperties {
|
|
|
42
48
|
useHTTPForDevDeploy?: boolean;
|
|
43
49
|
signingUsername?: string;
|
|
44
50
|
certificateName?: string;
|
|
51
|
+
authMethod?: 'basic' | 'oauth2' | 'cookie';
|
|
52
|
+
oauth2?: OAuth2Config;
|
|
53
|
+
sessionLoginUrl?: string;
|
|
54
|
+
baseEnvironment?: string;
|
|
55
|
+
production?: boolean;
|
|
56
|
+
environments?: Record<string, EnvironmentOverride>;
|
|
57
|
+
accessToken?: string;
|
|
58
|
+
sessionCookie?: string;
|
|
59
|
+
environmentName?: string;
|
|
60
|
+
productionEnvironment?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Fields an environment may override; everything else comes from the base. */
|
|
63
|
+
export type EnvironmentOverride = Partial<Pick<DevProperties, 'domain' | 'siteName' | 'addonName' | 'username' | 'authMethod' | 'oauth2' | 'sessionLoginUrl' | 'useHTTPForDevDeploy'>> & {
|
|
64
|
+
production?: boolean;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* OAuth2 provider config for bearer-token deploys.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately secret-free: the client secret and refresh token live in the OS
|
|
70
|
+
* keychain, so nothing here is unsafe to write to .dev_properties.json.
|
|
71
|
+
*/
|
|
72
|
+
export interface OAuth2Config {
|
|
73
|
+
authorizationEndpoint: string;
|
|
74
|
+
tokenEndpoint: string;
|
|
75
|
+
clientId: string;
|
|
76
|
+
scopes?: string[];
|
|
77
|
+
redirectPort?: number;
|
|
45
78
|
}
|
|
46
79
|
/**
|
|
47
80
|
* Signing credentials (password is runtime-only, not persisted)
|
|
@@ -59,7 +92,9 @@ export interface DeployConfig {
|
|
|
59
92
|
siteName: string;
|
|
60
93
|
addonName: string;
|
|
61
94
|
username: string;
|
|
62
|
-
password
|
|
95
|
+
password?: string;
|
|
96
|
+
accessToken?: string;
|
|
97
|
+
sessionCookie?: string;
|
|
63
98
|
useHTTP?: boolean;
|
|
64
99
|
}
|
|
65
100
|
/**
|
|
@@ -86,6 +121,7 @@ export interface ProjectInfo {
|
|
|
86
121
|
hasSigningProperties: boolean;
|
|
87
122
|
hasLegacyPassword: boolean;
|
|
88
123
|
devProperties?: DevProperties;
|
|
124
|
+
inheritedKeys: string[];
|
|
89
125
|
packageJson: PackageJson;
|
|
90
126
|
hasSitevisionScripts: boolean;
|
|
91
127
|
hasNodeModules: boolean;
|
|
@@ -143,6 +179,7 @@ export interface DeployResponse {
|
|
|
143
179
|
executableId?: string;
|
|
144
180
|
message?: string;
|
|
145
181
|
error?: string;
|
|
182
|
+
authExpired?: boolean;
|
|
146
183
|
}
|
|
147
184
|
/**
|
|
148
185
|
* API response from addon creation
|
|
@@ -151,6 +188,7 @@ export interface CreateAddonResponse {
|
|
|
151
188
|
success: boolean;
|
|
152
189
|
addonId?: string;
|
|
153
190
|
error?: string;
|
|
191
|
+
authExpired?: boolean;
|
|
154
192
|
}
|
|
155
193
|
/**
|
|
156
194
|
* API response from activation
|
|
@@ -158,6 +196,7 @@ export interface CreateAddonResponse {
|
|
|
158
196
|
export interface ActivationResponse {
|
|
159
197
|
success: boolean;
|
|
160
198
|
error?: string;
|
|
199
|
+
authExpired?: boolean;
|
|
161
200
|
}
|
|
162
201
|
/**
|
|
163
202
|
* Build mode
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { type Language } from './i18n.js';
|
|
2
|
+
/** User-facing preferences editable from the shell's Settings screen. */
|
|
3
|
+
export interface Settings {
|
|
4
|
+
language: Language;
|
|
5
|
+
introAnimation: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function getSettings(): Settings;
|
|
8
|
+
export declare function setSettings(patch: Partial<Settings>): void;
|
|
9
|
+
/** Path of the config file, for display. */
|
|
10
|
+
export declare function settingsFile(): string;
|
|
1
11
|
/**
|
|
2
12
|
* True until the user has completed the first-run welcome at least once.
|
|
3
13
|
*/
|
package/dist/utils/config.js
CHANGED
|
@@ -26,6 +26,20 @@ function writeConfig(config) {
|
|
|
26
26
|
// shows again next time, which is harmless.
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
export function getSettings() {
|
|
30
|
+
const config = readConfig();
|
|
31
|
+
return {
|
|
32
|
+
language: config.language ?? 'en',
|
|
33
|
+
introAnimation: config.introAnimation ?? true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function setSettings(patch) {
|
|
37
|
+
writeConfig({ ...readConfig(), ...patch });
|
|
38
|
+
}
|
|
39
|
+
/** Path of the config file, for display. */
|
|
40
|
+
export function settingsFile() {
|
|
41
|
+
return configFile();
|
|
42
|
+
}
|
|
29
43
|
/**
|
|
30
44
|
* True until the user has completed the first-run welcome at least once.
|
|
31
45
|
*/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DevProperties, EnvironmentOverride, ProjectInfo } from '../types/index.js';
|
|
2
|
+
/** Name of the environment the top-level dev properties describe. */
|
|
3
|
+
export declare function baseEnvironment(dev?: Partial<DevProperties>): string;
|
|
4
|
+
export declare function environmentNames(dev?: Partial<DevProperties>): string[];
|
|
5
|
+
export declare function isProductionEnvironment(name: string, dev?: Partial<DevProperties>): boolean;
|
|
6
|
+
/** Badge colour: dev green, production red, anything else yellow. */
|
|
7
|
+
export declare function environmentColor(name: string, dev?: Partial<DevProperties>): 'green' | 'yellow' | 'red';
|
|
8
|
+
/**
|
|
9
|
+
* The effective dev properties for one environment: the base with that
|
|
10
|
+
* environment's overrides applied and credentials resolved for its
|
|
11
|
+
* domain/username. The base ("dev") is returned as-is.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveEnvironment(dev: DevProperties, name: string): DevProperties;
|
|
14
|
+
/** A project view whose dev properties are resolved for `name`. */
|
|
15
|
+
export declare function environmentProject(project: ProjectInfo, name: string): ProjectInfo;
|
|
16
|
+
/**
|
|
17
|
+
* Write one environment's override into the base: keep only the keys whose
|
|
18
|
+
* value differs from the base, so the file stays minimal.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withEnvironmentOverride(base: DevProperties, name: string, values: EnvironmentOverride): DevProperties;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { resolveRuntimeSecrets } from './project-detection.js';
|
|
2
|
+
/** Name of the environment the top-level dev properties describe. */
|
|
3
|
+
export function baseEnvironment(dev) {
|
|
4
|
+
return dev?.baseEnvironment?.trim() || 'dev';
|
|
5
|
+
}
|
|
6
|
+
export function environmentNames(dev) {
|
|
7
|
+
const base = baseEnvironment(dev);
|
|
8
|
+
return [
|
|
9
|
+
base,
|
|
10
|
+
...Object.keys(dev?.environments ?? {}).filter(name => name !== base),
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
export function isProductionEnvironment(name, dev) {
|
|
14
|
+
if (name === baseEnvironment(dev))
|
|
15
|
+
return dev?.production ?? false;
|
|
16
|
+
return dev?.environments?.[name]?.production ?? /prod/i.test(name);
|
|
17
|
+
}
|
|
18
|
+
/** Badge colour: dev green, production red, anything else yellow. */
|
|
19
|
+
export function environmentColor(name, dev) {
|
|
20
|
+
if (isProductionEnvironment(name, dev))
|
|
21
|
+
return 'red';
|
|
22
|
+
return name === baseEnvironment(dev) ? 'green' : 'yellow';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The effective dev properties for one environment: the base with that
|
|
26
|
+
* environment's overrides applied and credentials resolved for its
|
|
27
|
+
* domain/username. The base ("dev") is returned as-is.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveEnvironment(dev, name) {
|
|
30
|
+
const production = isProductionEnvironment(name, dev);
|
|
31
|
+
if (name === baseEnvironment(dev)) {
|
|
32
|
+
return { ...dev, environmentName: name, productionEnvironment: production };
|
|
33
|
+
}
|
|
34
|
+
const override = dev.environments?.[name] ?? {};
|
|
35
|
+
const { production: _production, ...fields } = override;
|
|
36
|
+
const resolved = {
|
|
37
|
+
...dev,
|
|
38
|
+
...fields,
|
|
39
|
+
password: undefined,
|
|
40
|
+
accessToken: undefined,
|
|
41
|
+
sessionCookie: undefined,
|
|
42
|
+
environmentName: name,
|
|
43
|
+
productionEnvironment: production,
|
|
44
|
+
};
|
|
45
|
+
return resolveRuntimeSecrets(resolved);
|
|
46
|
+
}
|
|
47
|
+
/** A project view whose dev properties are resolved for `name`. */
|
|
48
|
+
export function environmentProject(project, name) {
|
|
49
|
+
if (!project.devProperties)
|
|
50
|
+
return project;
|
|
51
|
+
return {
|
|
52
|
+
...project,
|
|
53
|
+
devProperties: resolveEnvironment(project.devProperties, name),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Write one environment's override into the base: keep only the keys whose
|
|
58
|
+
* value differs from the base, so the file stays minimal.
|
|
59
|
+
*/
|
|
60
|
+
export function withEnvironmentOverride(base, name, values) {
|
|
61
|
+
const current = { ...base.environments?.[name] };
|
|
62
|
+
const changed = Object.fromEntries(Object.entries(values).map(([key, value]) => {
|
|
63
|
+
const baseValue = base[key];
|
|
64
|
+
const same = value === undefined ||
|
|
65
|
+
value === '' ||
|
|
66
|
+
JSON.stringify(value) === JSON.stringify(baseValue);
|
|
67
|
+
return [key, same ? undefined : value];
|
|
68
|
+
}));
|
|
69
|
+
const override = Object.fromEntries(Object.entries({ ...current, ...changed }).filter(([, value]) => value !== undefined));
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
environments: { ...base.environments, [name]: override },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI language. English strings are the keys; `t()` returns them untouched in
|
|
3
|
+
* English and looks them up in the Swedish table otherwise, falling back to
|
|
4
|
+
* English for anything untranslated. `{name}` placeholders are filled from
|
|
5
|
+
* the vars argument.
|
|
6
|
+
*/
|
|
7
|
+
export type Language = 'en' | 'sv';
|
|
8
|
+
export declare const LANGUAGES: Language[];
|
|
9
|
+
export declare const LANGUAGE_NAMES: Record<Language, string>;
|
|
10
|
+
export declare function getLanguage(): Language;
|
|
11
|
+
export declare function setLanguage(language: Language): void;
|
|
12
|
+
export declare function t(text: string, vars?: Record<string, string | number>): string;
|