sitevision-cli 1.0.0-beta.30 → 1.0.0-beta.32
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 +28 -0
- package/dist/cli.js +20 -2
- package/dist/scaffold/inquirer-stub.d.ts +6 -0
- package/dist/scaffold/inquirer-stub.js +119 -0
- package/dist/scaffold/register.d.ts +1 -0
- package/dist/scaffold/register.js +15 -0
- package/dist/shell/ConfigForm.d.ts +7 -1
- package/dist/shell/ConfigForm.js +95 -11
- package/dist/shell/Help.d.ts +4 -1
- package/dist/shell/Help.js +71 -7
- package/dist/shell/Shell.d.ts +2 -1
- package/dist/shell/Shell.js +249 -12
- package/dist/shell/actions.d.ts +1 -0
- package/dist/shell/actions.js +0 -0
- package/dist/utils/create-app.d.ts +53 -0
- package/dist/utils/create-app.js +219 -0
- package/dist/utils/i18n.js +46 -0
- package/dist/utils/process-runner.d.ts +2 -1
- package/dist/utils/process-runner.js +4 -1
- package/dist/utils/project-detection.d.ts +10 -2
- package/dist/utils/project-detection.js +88 -11
- package/dist/utils/tasks.d.ts +12 -1
- package/dist/utils/tasks.js +2 -2
- package/package.json +1 -1
package/dist/utils/i18n.js
CHANGED
|
@@ -103,6 +103,20 @@ const sv = {
|
|
|
103
103
|
'switch pane': 'byt panel',
|
|
104
104
|
tabs: 'flikar',
|
|
105
105
|
help: 'hjälp',
|
|
106
|
+
// Help guide
|
|
107
|
+
'Typing filters the app list. ↑↓ move, Enter selects the app and moves focus to the content pane. Action keys such as d or p only work there; in the navigator they search.': 'Skriv för att filtrera applistan. ↑↓ flyttar, Enter väljer appen och flyttar fokus till innehållspanelen. Åtgärdstangenter som d eller p fungerar bara där; i navigatorn söker de.',
|
|
108
|
+
'The last row is Workspace settings: the shared root config that every app inherits.': 'Sista raden är Arbetsytans inställningar: den delade rotkonfigurationen som alla appar ärver.',
|
|
109
|
+
"The root .dev_properties.json shared by every app, with the root package.json defaults underneath. Fill in site, auth and environments once; an app's own config overrides only what differs.": 'Rotens .dev_properties.json som delas av alla appar, med rotens package.json som standardvärden under. Fyll i webbplats, autentisering och miljöer en gång; en apps egen konfig skriver bara över det som skiljer.',
|
|
110
|
+
'Same keys as the Config tab: Enter edits and saves, Esc cancels, ←→ or space cycles choices.': 'Samma tangenter som Konfig-fliken: Enter redigerar och sparar, Esc avbryter, ←→ eller mellanslag bläddrar bland val.',
|
|
111
|
+
'Each status line names the key that fixes it: i installs dependencies, e opens Config, y syncs package.json, / sets up signing.': 'Varje statusrad anger tangenten som åtgärdar den: i installerar beroenden, e öppnar Konfig, y synkar package.json, / sätter upp signering.',
|
|
112
|
+
'The badge in the top bar is the active environment: green for the base, yellow for others, red for production. v cycles it.': 'Märket i toppraden är den aktiva miljön: grönt för basen, gult för övriga, rött för produktion. v växlar.',
|
|
113
|
+
'Values come in layers and the source is shown next to each: your local .dev_properties.json wins, shared package.json values sit underneath, and in a workspace the root config underneath that. Environments add on top of the base and override only what differs.': 'Värden kommer i lager och källan visas bredvid varje: din lokala .dev_properties.json vinner, delade värden i package.json ligger under, och i en arbetsyta rotkonfigurationen under det. Miljöer läggs ovanpå basen och skriver bara över det som skiljer.',
|
|
114
|
+
'Enter edits and saves a field, Esc cancels, ←→ or space cycles choices, Ctrl+O lists the addons on the site. y copies shared values into package.json for the team.': 'Enter redigerar och sparar ett fält, Esc avbryter, ←→ eller mellanslag bläddrar bland val, Ctrl+O listar webbplatsens tillägg. y kopierar delade värden till package.json för teamet.',
|
|
115
|
+
'Passwords and secrets go to the OS keychain, never to a file. Leave them empty to be asked on each run.': 'Lösenord och hemligheter sparas i nyckelringen, aldrig i en fil. Lämna dem tomma för att bli tillfrågad varje gång.',
|
|
116
|
+
"The versions uploaded to the addon on the active environment's site, the domain in the top bar. v switches environment.": 'Versionerna som laddats upp till tillägget på den aktiva miljöns webbplats, domänen i toppraden. v byter miljö.',
|
|
117
|
+
'↑↓ select, a activates the selected version, r refreshes. Production deploys activate on their own.': '↑↓ väljer, a aktiverar vald version, r uppdaterar. Produktionsdriftsättningar aktiverar själva.',
|
|
118
|
+
'Output from build, sign, deploy, dev and watch for the selected app. Dev and watch keep running while you switch apps; K stops them.': 'Utdata från build, sign, deploy, dev och watch för vald app. Dev och watch fortsätter köra medan du byter app; K stoppar dem.',
|
|
119
|
+
'↑↓ scroll, PgUp/PgDn page, f jumps to the end, x toggles line wrap.': '↑↓ scrollar, PgUp/PgDn bläddrar sida, f hoppar till slutet, x växlar radbrytning.',
|
|
106
120
|
"What's new": 'Nyheter',
|
|
107
121
|
'changelog for every release': 'ändringslogg för varje version',
|
|
108
122
|
Changelog: 'Ändringslogg',
|
|
@@ -188,6 +202,38 @@ const sv = {
|
|
|
188
202
|
'required for signed deploys': 'krävs för signerad driftsättning',
|
|
189
203
|
'blank = prompt each run': 'tomt = fråga varje gång',
|
|
190
204
|
SIGNING: 'SIGNERING',
|
|
205
|
+
MANIFEST: 'MANIFEST',
|
|
206
|
+
// New app
|
|
207
|
+
'Name of the new app': 'Namn på den nya appen',
|
|
208
|
+
'Create it in folder': 'Skapa den i mappen',
|
|
209
|
+
'Other folder…': 'Annan mapp…',
|
|
210
|
+
'Type a name for the app, for example my-new-app.': 'Skriv ett namn på appen, till exempel min-nya-app.',
|
|
211
|
+
'Try "{suggestion}".': 'Prova "{suggestion}".',
|
|
212
|
+
"An app name can't have spaces.": 'Ett appnamn kan inte innehålla mellanslag.',
|
|
213
|
+
"An app name can't contain {chars}: it becomes the folder, the package name and the app id.": 'Ett appnamn kan inte innehålla {chars}: det blir mappen, paketnamnet och appens id.',
|
|
214
|
+
'Lowercase is safer: npm expects it, and "{name}" and "{lower}" are one folder on macOS and Windows but two on Linux. Try "{suggestion}", or press Enter again to keep "{name}".': 'Gemener är säkrare: npm förväntar sig det, och "{name}" och "{lower}" är samma mapp på macOS och Windows men två på Linux. Prova "{suggestion}", eller tryck Enter igen för att behålla "{name}".',
|
|
215
|
+
'Start the name with a letter or a digit.': 'Börja namnet med en bokstav eller en siffra.',
|
|
216
|
+
'{dir} already exists. Pick another name, or keep it and choose a different folder next.': '{dir} finns redan. Välj ett annat namn, eller behåll det och välj en annan mapp i nästa steg.',
|
|
217
|
+
'creating {app}: installing, questions follow': 'skapar {app}: installerar, frågor kommer strax',
|
|
218
|
+
'The scaffolder could not be run from here': 'Verktyget kunde inte köras härifrån',
|
|
219
|
+
'creating {app} failed, see the log': 'kunde inte skapa {app}, se loggen',
|
|
220
|
+
'{app} created in {dir}': '{app} skapad i {dir}',
|
|
221
|
+
'{app} created': '{app} skapad',
|
|
222
|
+
'Create the addon "{addon}" on {domain} now?': 'Skapa tillägget "{addon}" på {domain} nu?',
|
|
223
|
+
'↑↓ move · Space toggle · Enter submit · Esc cancel': '↑↓ flytta · Mellanslag markera · Enter skicka · Esc avbryt',
|
|
224
|
+
'App id': 'App-id',
|
|
225
|
+
Version: 'Version',
|
|
226
|
+
Name: 'Namn',
|
|
227
|
+
Description: 'Beskrivning',
|
|
228
|
+
Author: 'Författare',
|
|
229
|
+
'Help URL': 'Hjälp-URL',
|
|
230
|
+
'Not saved: {label} is required.': 'Inte sparat: {label} krävs.',
|
|
231
|
+
'Identifier of the app in Sitevision; it also names the zip. Changing it makes the next deploy a new app instead of an update.': 'Appens identifierare i Sitevision; den namnger också zip-filen. Ändras den blir nästa driftsättning en ny app i stället för en uppdatering.',
|
|
232
|
+
'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.': 'Appens version. Id och version identifierar tillsammans en uppladdning, så höj den för att driftsätta en ny version i stället för att skriva över den nuvarande.',
|
|
233
|
+
'Name shown when importing and administering the app in the Sitevision editor. A multilingual manifest needs at least English.': 'Namnet som visas när appen importeras och administreras i Sitevisions redigerare. Ett flerspråkigt manifest behöver minst engelska.',
|
|
234
|
+
'Short description shown next to the name in the Sitevision editor.': 'Kort beskrivning som visas bredvid namnet i Sitevisions redigerare.',
|
|
235
|
+
'Who made the app, shown in the Sitevision editor.': 'Vem som gjort appen, visas i Sitevisions redigerare.',
|
|
236
|
+
"Link to the app's documentation, shown in the Sitevision editor.": 'Länk till appens dokumentation, visas i Sitevisions redigerare.',
|
|
191
237
|
ENVIRONMENT: 'MILJÖ',
|
|
192
238
|
'Environment name': 'Miljönamn',
|
|
193
239
|
Production: 'Produktion',
|
|
@@ -24,7 +24,8 @@ export declare class ProcessRunner extends EventEmitter {
|
|
|
24
24
|
private readonly command;
|
|
25
25
|
private readonly args;
|
|
26
26
|
private readonly cwd?;
|
|
27
|
-
|
|
27
|
+
private readonly env?;
|
|
28
|
+
constructor(command: string, args?: string[], cwd?: string, env?: NodeJS.ProcessEnv);
|
|
28
29
|
run(): Promise<ProcessResult>;
|
|
29
30
|
kill(): void;
|
|
30
31
|
getOutput(): ProcessOutput[];
|
|
@@ -53,16 +53,19 @@ export class ProcessRunner extends EventEmitter {
|
|
|
53
53
|
command;
|
|
54
54
|
args;
|
|
55
55
|
cwd;
|
|
56
|
-
|
|
56
|
+
env;
|
|
57
|
+
constructor(command, args = [], cwd, env) {
|
|
57
58
|
super();
|
|
58
59
|
this.command = command;
|
|
59
60
|
this.args = args;
|
|
60
61
|
this.cwd = cwd;
|
|
62
|
+
this.env = env;
|
|
61
63
|
}
|
|
62
64
|
run() {
|
|
63
65
|
return new Promise((resolve, reject) => {
|
|
64
66
|
this.process = spawnChild(this.command, this.args, {
|
|
65
67
|
cwd: this.cwd || process.cwd(),
|
|
68
|
+
env: this.env,
|
|
66
69
|
shell: true,
|
|
67
70
|
stdio: 'pipe',
|
|
68
71
|
});
|
|
@@ -99,6 +99,13 @@ export declare function readManifest(cwd: string): {
|
|
|
99
99
|
manifestPath: string;
|
|
100
100
|
manifest: SitevisionManifest;
|
|
101
101
|
} | null;
|
|
102
|
+
/**
|
|
103
|
+
* Set one text field of manifest.json, or one language of a localized field.
|
|
104
|
+
* An empty value removes the key. A value that is already a string is replaced
|
|
105
|
+
* in the text, so comments and formatting survive; anything else rewrites the
|
|
106
|
+
* file, which is refused when that would drop comments.
|
|
107
|
+
*/
|
|
108
|
+
export declare function writeManifestField(manifestPath: string, key: string, value: string, lang?: string): void;
|
|
102
109
|
/**
|
|
103
110
|
* Detect if the current directory is a Sitevision project
|
|
104
111
|
*/
|
|
@@ -169,8 +176,9 @@ export declare const IMPLICIT_VALUES: Record<string, unknown>;
|
|
|
169
176
|
export declare function getPackageJsonSyncChanges(dir: string): PackageJsonSyncChange[];
|
|
170
177
|
export declare function hasPackageJson(dir: string): boolean;
|
|
171
178
|
/**
|
|
172
|
-
* Copy the pending shared values from .dev_properties.json
|
|
173
|
-
*
|
|
179
|
+
* Copy the pending shared values from .dev_properties.json, and the manifest
|
|
180
|
+
* fields package.json mirrors, into package.json, creating package.json when
|
|
181
|
+
* the directory has none. Throws if it cannot be
|
|
174
182
|
* read or written.
|
|
175
183
|
*/
|
|
176
184
|
export declare function syncDevPropertiesToPackageJson(dir: string): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getDeployPassword, setDeployPassword, getSessionCookie, } from './keychain.js';
|
|
4
|
-
import { parseJsonc } from './jsonc.js';
|
|
4
|
+
import { parseJsonc, stripJsonComments } from './jsonc.js';
|
|
5
5
|
import { getLanguage } from './i18n.js';
|
|
6
6
|
// =============================================================================
|
|
7
7
|
// LOCALIZED TEXT
|
|
@@ -302,6 +302,40 @@ export function readManifest(cwd) {
|
|
|
302
302
|
}
|
|
303
303
|
return null;
|
|
304
304
|
}
|
|
305
|
+
const JSON_STRING = String.raw `"(?:[^"\\]|\\.)*"`;
|
|
306
|
+
const escapeRegExp = (s) => s.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
|
|
307
|
+
/**
|
|
308
|
+
* Set one text field of manifest.json, or one language of a localized field.
|
|
309
|
+
* An empty value removes the key. A value that is already a string is replaced
|
|
310
|
+
* in the text, so comments and formatting survive; anything else rewrites the
|
|
311
|
+
* file, which is refused when that would drop comments.
|
|
312
|
+
*/
|
|
313
|
+
export function writeManifestField(manifestPath, key, value, lang) {
|
|
314
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
315
|
+
const wanted = parseJsonc(raw);
|
|
316
|
+
const holder = lang ? wanted[key] : wanted;
|
|
317
|
+
const leaf = lang ?? key;
|
|
318
|
+
// JSON.stringify drops an undefined key.
|
|
319
|
+
holder[leaf] = value || undefined;
|
|
320
|
+
const prefix = lang
|
|
321
|
+
? String.raw `"${escapeRegExp(key)}"\s*:\s*\{[^}]*?"${escapeRegExp(lang)}"\s*:\s*`
|
|
322
|
+
: String.raw `"${escapeRegExp(key)}"\s*:\s*`;
|
|
323
|
+
const patched = raw.replace(new RegExp(`(${prefix})${JSON_STRING}`), (_, before) => before + JSON.stringify(value));
|
|
324
|
+
let patchedOk = false;
|
|
325
|
+
try {
|
|
326
|
+
patchedOk = JSON.stringify(parseJsonc(patched)) === JSON.stringify(wanted);
|
|
327
|
+
}
|
|
328
|
+
catch { }
|
|
329
|
+
if (patchedOk) {
|
|
330
|
+
fs.writeFileSync(manifestPath, patched);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (stripJsonComments(raw) !== raw) {
|
|
334
|
+
throw new Error(`${manifestPath} has comments; add or remove "${leaf}" by hand`);
|
|
335
|
+
}
|
|
336
|
+
const indent = /^(?<indent>[\t ]+)/m.exec(raw)?.groups?.['indent'] ?? '\t';
|
|
337
|
+
fs.writeFileSync(manifestPath, JSON.stringify(wanted, null, indent) + (raw.endsWith('\n') ? '\n' : ''));
|
|
338
|
+
}
|
|
305
339
|
/**
|
|
306
340
|
* Detect if the current directory is a Sitevision project
|
|
307
341
|
*/
|
|
@@ -568,6 +602,38 @@ function pendingSync(dir) {
|
|
|
568
602
|
}
|
|
569
603
|
return pending;
|
|
570
604
|
}
|
|
605
|
+
// Manifest fields package.json mirrors under the same name; the manifest wins.
|
|
606
|
+
const MANIFEST_PACKAGE_KEYS = ['version', 'description', 'author'];
|
|
607
|
+
/**
|
|
608
|
+
* Manifest values the app's own package.json does not mirror yet. Nothing when
|
|
609
|
+
* either file is missing, so syncing never creates a package.json for this.
|
|
610
|
+
*/
|
|
611
|
+
function pendingManifestSync(dir) {
|
|
612
|
+
const packageJson = readPackageJson(dir);
|
|
613
|
+
if (!packageJson)
|
|
614
|
+
return [];
|
|
615
|
+
let manifest;
|
|
616
|
+
try {
|
|
617
|
+
manifest = readManifest(dir)?.manifest;
|
|
618
|
+
}
|
|
619
|
+
catch { }
|
|
620
|
+
if (!manifest)
|
|
621
|
+
return [];
|
|
622
|
+
const pending = [];
|
|
623
|
+
for (const key of MANIFEST_PACKAGE_KEYS) {
|
|
624
|
+
const value = key === 'description'
|
|
625
|
+
? localizedText(manifest.description, 'en')
|
|
626
|
+
: manifest[key];
|
|
627
|
+
const current = packageJson[key];
|
|
628
|
+
// ponytail: an author object ({name, email}) is left alone.
|
|
629
|
+
if (!value || typeof value !== 'string' || value === current)
|
|
630
|
+
continue;
|
|
631
|
+
if (current !== undefined && typeof current !== 'string')
|
|
632
|
+
continue;
|
|
633
|
+
pending.push({ key, from: current, value });
|
|
634
|
+
}
|
|
635
|
+
return pending;
|
|
636
|
+
}
|
|
571
637
|
/** What an unset field means, so spelling out the default is not a change. */
|
|
572
638
|
export const IMPLICIT_VALUES = {
|
|
573
639
|
authMethod: 'basic',
|
|
@@ -579,27 +645,38 @@ const display = (value) => typeof value === 'string' ? value : JSON.stringify(va
|
|
|
579
645
|
/** What syncing this directory would change in its package.json. */
|
|
580
646
|
export function getPackageJsonSyncChanges(dir) {
|
|
581
647
|
const current = readPackageDefaults(dir);
|
|
582
|
-
return
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
648
|
+
return [
|
|
649
|
+
...pendingSync(dir).map(({ key, value }) => current[key] === undefined
|
|
650
|
+
? { key: packageLabel(key), to: display(value) }
|
|
651
|
+
: {
|
|
652
|
+
key: packageLabel(key),
|
|
653
|
+
from: display(current[key]),
|
|
654
|
+
to: display(value),
|
|
655
|
+
}),
|
|
656
|
+
...pendingManifestSync(dir).map(({ key, from, value }) => ({
|
|
657
|
+
key,
|
|
658
|
+
...(from !== undefined && { from }),
|
|
659
|
+
to: value,
|
|
660
|
+
})),
|
|
661
|
+
];
|
|
589
662
|
}
|
|
590
663
|
export function hasPackageJson(dir) {
|
|
591
664
|
return fs.existsSync(path.join(dir, 'package.json'));
|
|
592
665
|
}
|
|
593
666
|
/**
|
|
594
|
-
* Copy the pending shared values from .dev_properties.json
|
|
595
|
-
*
|
|
667
|
+
* Copy the pending shared values from .dev_properties.json, and the manifest
|
|
668
|
+
* fields package.json mirrors, into package.json, creating package.json when
|
|
669
|
+
* the directory has none. Throws if it cannot be
|
|
596
670
|
* read or written.
|
|
597
671
|
*/
|
|
598
672
|
export function syncDevPropertiesToPackageJson(dir) {
|
|
599
673
|
const pending = pendingSync(dir);
|
|
600
|
-
|
|
674
|
+
const mirrored = pendingManifestSync(dir);
|
|
675
|
+
if (pending.length + mirrored.length === 0 && hasPackageJson(dir))
|
|
601
676
|
return false;
|
|
602
677
|
updatePackageJson(dir, packageJson => {
|
|
678
|
+
for (const { key, value } of mirrored)
|
|
679
|
+
packageJson[key] = value;
|
|
603
680
|
for (const { key, value } of pending) {
|
|
604
681
|
const topKey = PACKAGE_TOP_KEYS[key];
|
|
605
682
|
if (topKey) {
|
package/dist/utils/tasks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProjectInfo, SigningCredentials, DeployConfig } from '../types/index.js';
|
|
2
|
-
export type TaskKind = 'dev' | 'watch' | 'build' | 'sign' | 'deploy' | 'activate' | 'install';
|
|
2
|
+
export type TaskKind = 'dev' | 'watch' | 'build' | 'sign' | 'deploy' | 'activate' | 'install' | 'create';
|
|
3
3
|
export type TaskStatus = 'running' | 'success' | 'error' | 'stopped';
|
|
4
4
|
export type LogLevel = 'info' | 'ok' | 'warn' | 'error';
|
|
5
5
|
export interface LogLine {
|
|
@@ -26,6 +26,16 @@ export declare function getTasks(): Task[];
|
|
|
26
26
|
export declare function useTasks(): Task[];
|
|
27
27
|
export declare function runningTasks(appRoot?: string): Task[];
|
|
28
28
|
export declare function clearFinished(): void;
|
|
29
|
+
type Log = (tag: string, text: string, level?: LogLevel) => void;
|
|
30
|
+
export declare function createTask(kind: TaskKind, project: {
|
|
31
|
+
root: string;
|
|
32
|
+
manifest: Pick<ProjectInfo['manifest'], 'name' | 'id'>;
|
|
33
|
+
}, label: string, stop?: () => void): {
|
|
34
|
+
task: Task;
|
|
35
|
+
log: Log;
|
|
36
|
+
finish: (status: TaskStatus, error?: string) => void;
|
|
37
|
+
};
|
|
38
|
+
export declare function setPhase(task: Task, phase: string): void;
|
|
29
39
|
export interface DeployOptions {
|
|
30
40
|
force?: boolean;
|
|
31
41
|
production?: boolean;
|
|
@@ -48,3 +58,4 @@ export interface DevOptions {
|
|
|
48
58
|
* deploy. Runs until `task.stop()`; the task stays in the registry meanwhile.
|
|
49
59
|
*/
|
|
50
60
|
export declare function startDev(project: ProjectInfo, options: DevOptions): Task;
|
|
61
|
+
export {};
|
package/dist/utils/tasks.js
CHANGED
|
@@ -32,7 +32,7 @@ export function clearFinished() {
|
|
|
32
32
|
tasks = tasks.filter(t => t.status === 'running');
|
|
33
33
|
notify();
|
|
34
34
|
}
|
|
35
|
-
function createTask(kind, project, label, stop = () => { }) {
|
|
35
|
+
export function createTask(kind, project, label, stop = () => { }) {
|
|
36
36
|
const task = {
|
|
37
37
|
id: nextId++,
|
|
38
38
|
kind,
|
|
@@ -69,7 +69,7 @@ function createTask(kind, project, label, stop = () => { }) {
|
|
|
69
69
|
};
|
|
70
70
|
return { task, log, finish };
|
|
71
71
|
}
|
|
72
|
-
function setPhase(task, phase) {
|
|
72
|
+
export function setPhase(task, phase) {
|
|
73
73
|
task.phase = phase;
|
|
74
74
|
notify();
|
|
75
75
|
}
|