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
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
import { getGlobalSigning } from './config.js';
|
|
6
7
|
// =============================================================================
|
|
7
8
|
// LOCALIZED TEXT
|
|
8
9
|
// =============================================================================
|
|
@@ -22,6 +23,20 @@ export function localizedText(value, preferred = getLanguage()) {
|
|
|
22
23
|
return value;
|
|
23
24
|
return value[preferred] ?? value['en'] ?? Object.values(value)[0] ?? '';
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The manifest's names when the addon name is none of them. The two are
|
|
28
|
+
* separate things (the addon is an object on the site, the name a label in the
|
|
29
|
+
* zip), so a difference is only ever pointed out, never "fixed".
|
|
30
|
+
*/
|
|
31
|
+
export function addonNameDrift(addonName, manifest) {
|
|
32
|
+
const same = (text) => text.trim().toLowerCase();
|
|
33
|
+
const names = (typeof manifest?.name === 'string'
|
|
34
|
+
? [manifest.name]
|
|
35
|
+
: Object.values(manifest?.name ?? {})).filter(name => name.trim() !== '');
|
|
36
|
+
if (!addonName?.trim() || names.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
return names.some(name => same(name) === same(addonName)) ? undefined : names;
|
|
39
|
+
}
|
|
25
40
|
// =============================================================================
|
|
26
41
|
// PATH UTILITIES
|
|
27
42
|
// =============================================================================
|
|
@@ -105,16 +120,33 @@ function readDevPropertiesFile(dir) {
|
|
|
105
120
|
return null;
|
|
106
121
|
}
|
|
107
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Merge config layers, later ones winning. `environments` merges per name, so a
|
|
125
|
+
* layer that overrides one field of an environment keeps the rest of it.
|
|
126
|
+
*/
|
|
127
|
+
export function mergeDevLayers(...layers) {
|
|
128
|
+
let merged = {};
|
|
129
|
+
for (const layer of layers) {
|
|
130
|
+
if (!layer)
|
|
131
|
+
continue;
|
|
132
|
+
const environments = { ...merged.environments };
|
|
133
|
+
for (const [name, override] of Object.entries(layer.environments ?? {})) {
|
|
134
|
+
environments[name] = { ...environments[name], ...override };
|
|
135
|
+
}
|
|
136
|
+
merged = { ...merged, ...layer };
|
|
137
|
+
if (Object.keys(environments).length > 0) {
|
|
138
|
+
merged.environments = environments;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return merged;
|
|
142
|
+
}
|
|
108
143
|
/**
|
|
109
144
|
* The dev properties a workspace root defines (its own file merged over any
|
|
110
145
|
* ancestors'), with the deploy password resolved from the keychain like an
|
|
111
146
|
* app's would be. Used to edit shared config from the shell.
|
|
112
147
|
*/
|
|
113
148
|
export function readWorkspaceDevProperties(root) {
|
|
114
|
-
const merged =
|
|
115
|
-
...readInheritedDevProperties(root),
|
|
116
|
-
...readDevPropertiesFile(root),
|
|
117
|
-
};
|
|
149
|
+
const merged = mergeDevLayers(readInheritedDevProperties(root), readDevPropertiesFile(root));
|
|
118
150
|
if (merged.domain && merged.username && !merged.password) {
|
|
119
151
|
merged.password =
|
|
120
152
|
process.env['SITEVISION_DEPLOY_PASSWORD'] ??
|
|
@@ -125,37 +157,31 @@ export function readWorkspaceDevProperties(root) {
|
|
|
125
157
|
}
|
|
126
158
|
/** Ancestor directories' .dev_properties.json merged, nearest wins (no own file). */
|
|
127
159
|
export function readAncestorDevProperties(root) {
|
|
128
|
-
|
|
129
|
-
for (const dir of ancestorDirs(root)) {
|
|
130
|
-
merged = { ...merged, ...readDevPropertiesFile(dir) };
|
|
131
|
-
}
|
|
132
|
-
return merged;
|
|
160
|
+
return mergeDevLayers(...ancestorDirs(root).map(dir => readDevPropertiesFile(dir)));
|
|
133
161
|
}
|
|
134
162
|
/** Shared defaults from package.json: the workspace root's first, the app's on top. */
|
|
135
163
|
export function readPackageDefaultsChain(root) {
|
|
136
|
-
|
|
137
|
-
for (const dir of [...ancestorDirs(root), root]) {
|
|
138
|
-
merged = { ...merged, ...readPackageDefaults(dir) };
|
|
139
|
-
}
|
|
140
|
-
return merged;
|
|
164
|
+
return mergeDevLayers(...[...ancestorDirs(root), root].map(dir => readPackageDefaults(dir)));
|
|
141
165
|
}
|
|
142
166
|
/**
|
|
143
167
|
* Everything an app's own .dev_properties.json sits on top of: package.json
|
|
144
168
|
* defaults (root, then app), then ancestor .dev_properties.json files.
|
|
145
169
|
*/
|
|
146
170
|
export function readInheritedDevProperties(root) {
|
|
147
|
-
return
|
|
148
|
-
...readPackageDefaultsChain(root),
|
|
149
|
-
...readAncestorDevProperties(root),
|
|
150
|
-
};
|
|
171
|
+
return mergeDevLayers(readPackageDefaultsChain(root), readAncestorDevProperties(root));
|
|
151
172
|
}
|
|
152
173
|
/**
|
|
153
174
|
* Get app ID configuration from environment or defaults
|
|
154
175
|
*/
|
|
155
|
-
function getAppIdConfig() {
|
|
176
|
+
export function getAppIdConfig() {
|
|
156
177
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
// svc's own names first, then the ones sitevision-scripts reads.
|
|
179
|
+
prefix: process.env['SITEVISION_APP_ID_PREFIX'] ||
|
|
180
|
+
process.env['APP_ID_PREFIX'] ||
|
|
181
|
+
'',
|
|
182
|
+
suffix: process.env['SITEVISION_APP_ID_SUFFIX'] ||
|
|
183
|
+
process.env['APP_ID_SUFFIX'] ||
|
|
184
|
+
'',
|
|
159
185
|
};
|
|
160
186
|
}
|
|
161
187
|
/**
|
|
@@ -270,7 +296,7 @@ export function buildImportEndpointUrl(domain, siteName, addonName, appType, use
|
|
|
270
296
|
export class ManifestParseError extends Error {
|
|
271
297
|
constructor(manifestPath, cause) {
|
|
272
298
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
273
|
-
super(`${manifestPath}
|
|
299
|
+
super(`${manifestPath} ${reason}`);
|
|
274
300
|
this.name = 'ManifestParseError';
|
|
275
301
|
}
|
|
276
302
|
}
|
|
@@ -290,18 +316,65 @@ export function readManifest(cwd) {
|
|
|
290
316
|
}
|
|
291
317
|
// Manifests may contain comments (Sitevision's own docs show them), so
|
|
292
318
|
// parse as JSONC.
|
|
319
|
+
let manifest;
|
|
293
320
|
try {
|
|
294
|
-
|
|
295
|
-
manifestPath,
|
|
296
|
-
manifest: parseJsonc(fs.readFileSync(manifestPath, 'utf-8')),
|
|
297
|
-
};
|
|
321
|
+
manifest = parseJsonc(fs.readFileSync(manifestPath, 'utf-8'));
|
|
298
322
|
}
|
|
299
323
|
catch (error) {
|
|
300
|
-
throw new ManifestParseError(manifestPath, error);
|
|
324
|
+
throw new ManifestParseError(manifestPath, `is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
325
|
+
}
|
|
326
|
+
if (typeof manifest !== 'object' || manifest === null) {
|
|
327
|
+
throw new ManifestParseError(manifestPath, 'must be a JSON object');
|
|
301
328
|
}
|
|
329
|
+
// Zip names, endpoints and the build all derive from these.
|
|
330
|
+
for (const key of ['id', 'version', 'type']) {
|
|
331
|
+
if (typeof manifest[key] !== 'string' || manifest[key] === '') {
|
|
332
|
+
throw new ManifestParseError(manifestPath, `is missing "${key}"`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { manifestPath, manifest };
|
|
302
336
|
}
|
|
303
337
|
return null;
|
|
304
338
|
}
|
|
339
|
+
const JSON_STRING = String.raw `"(?:[^"\\]|\\.)*"`;
|
|
340
|
+
const escapeRegExp = (s) => s.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
|
|
341
|
+
/**
|
|
342
|
+
* Set one text field of manifest.json, or one language of a localized field.
|
|
343
|
+
* An empty value removes the key. A value that is already a string is replaced
|
|
344
|
+
* in the text, so comments and formatting survive; anything else rewrites the
|
|
345
|
+
* file, which is refused when that would drop comments.
|
|
346
|
+
*/
|
|
347
|
+
export function writeManifestField(manifestPath, key, value, lang) {
|
|
348
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
349
|
+
const wanted = parseJsonc(raw);
|
|
350
|
+
const existing = wanted[key];
|
|
351
|
+
if (lang && typeof existing !== 'object') {
|
|
352
|
+
// A plain string becomes the English text of a multilingual value.
|
|
353
|
+
wanted[key] = existing ? { en: existing } : {};
|
|
354
|
+
}
|
|
355
|
+
const holder = lang ? wanted[key] : wanted;
|
|
356
|
+
const leaf = lang ?? key;
|
|
357
|
+
// JSON.stringify drops an undefined key.
|
|
358
|
+
holder[leaf] = value || undefined;
|
|
359
|
+
const prefix = lang
|
|
360
|
+
? String.raw `"${escapeRegExp(key)}"\s*:\s*\{[^}]*?"${escapeRegExp(lang)}"\s*:\s*`
|
|
361
|
+
: String.raw `"${escapeRegExp(key)}"\s*:\s*`;
|
|
362
|
+
const patched = raw.replace(new RegExp(`(${prefix})${JSON_STRING}`), (_, before) => before + JSON.stringify(value));
|
|
363
|
+
let patchedOk = false;
|
|
364
|
+
try {
|
|
365
|
+
patchedOk = JSON.stringify(parseJsonc(patched)) === JSON.stringify(wanted);
|
|
366
|
+
}
|
|
367
|
+
catch { }
|
|
368
|
+
if (patchedOk) {
|
|
369
|
+
fs.writeFileSync(manifestPath, patched);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (stripJsonComments(raw) !== raw) {
|
|
373
|
+
throw new Error(`${manifestPath} has comments; add or remove "${leaf}" by hand`);
|
|
374
|
+
}
|
|
375
|
+
const indent = /^(?<indent>[\t ]+)/m.exec(raw)?.groups?.['indent'] ?? '\t';
|
|
376
|
+
fs.writeFileSync(manifestPath, JSON.stringify(wanted, null, indent) + (raw.endsWith('\n') ? '\n' : ''));
|
|
377
|
+
}
|
|
305
378
|
/**
|
|
306
379
|
* Detect if the current directory is a Sitevision project
|
|
307
380
|
*/
|
|
@@ -335,7 +408,9 @@ export function detectProject(cwd = process.cwd()) {
|
|
|
335
408
|
let hasLegacyPassword = false;
|
|
336
409
|
if (hasDevProperties) {
|
|
337
410
|
try {
|
|
338
|
-
|
|
411
|
+
// ponytail: the global signing identity only reaches a project that
|
|
412
|
+
// has some config of its own, so "configured?" checks stay truthful.
|
|
413
|
+
const parsed = mergeDevLayers(getGlobalSigning(), inherited, own);
|
|
339
414
|
hasLegacyPassword =
|
|
340
415
|
typeof parsed.password === 'string' && parsed.password.length > 0;
|
|
341
416
|
devProperties = parsed;
|
|
@@ -412,7 +487,7 @@ export function requireProject(cwd) {
|
|
|
412
487
|
* the whole shell down.
|
|
413
488
|
*/
|
|
414
489
|
export function appTypeOf(manifest) {
|
|
415
|
-
const type = manifest.type.toLowerCase();
|
|
490
|
+
const type = String(manifest.type ?? '').toLowerCase();
|
|
416
491
|
if (type.startsWith('web'))
|
|
417
492
|
return 'web';
|
|
418
493
|
if (type.startsWith('widget'))
|
|
@@ -471,10 +546,18 @@ export function writeDevProperties(projectRoot, properties,
|
|
|
471
546
|
// that would shadow the inherited value.
|
|
472
547
|
const inherited = complete
|
|
473
548
|
? {}
|
|
474
|
-
: readInheritedDevProperties(projectRoot);
|
|
549
|
+
: mergeDevLayers(getGlobalSigning(), readInheritedDevProperties(projectRoot));
|
|
475
550
|
const own = Object.fromEntries(Object.entries(persisted).filter(([key, value]) => value !== '' &&
|
|
476
551
|
(!Object.hasOwn(inherited, key) ||
|
|
477
552
|
JSON.stringify(inherited[key]) !== JSON.stringify(value))));
|
|
553
|
+
// A plaintext password already in the file (sitevision-scripts puts it
|
|
554
|
+
// there) moves to the keychain; if that fails it stays rather than be lost.
|
|
555
|
+
const legacy = readDevPropertiesFile(projectRoot)?.password;
|
|
556
|
+
if (legacy) {
|
|
557
|
+
const { domain, username } = { ...inherited, ...persisted };
|
|
558
|
+
if (!setDeployPassword(domain, username, legacy))
|
|
559
|
+
own['password'] = legacy;
|
|
560
|
+
}
|
|
478
561
|
fs.writeFileSync(devPropertiesPath, JSON.stringify(own, null, 2));
|
|
479
562
|
}
|
|
480
563
|
export function readSvcConfig(projectRoot) {
|
|
@@ -568,6 +651,38 @@ function pendingSync(dir) {
|
|
|
568
651
|
}
|
|
569
652
|
return pending;
|
|
570
653
|
}
|
|
654
|
+
// Manifest fields package.json mirrors under the same name; the manifest wins.
|
|
655
|
+
const MANIFEST_PACKAGE_KEYS = ['version', 'description', 'author'];
|
|
656
|
+
/**
|
|
657
|
+
* Manifest values the app's own package.json does not mirror yet. Nothing when
|
|
658
|
+
* either file is missing, so syncing never creates a package.json for this.
|
|
659
|
+
*/
|
|
660
|
+
function pendingManifestSync(dir) {
|
|
661
|
+
const packageJson = readPackageJson(dir);
|
|
662
|
+
if (!packageJson)
|
|
663
|
+
return [];
|
|
664
|
+
let manifest;
|
|
665
|
+
try {
|
|
666
|
+
manifest = readManifest(dir)?.manifest;
|
|
667
|
+
}
|
|
668
|
+
catch { }
|
|
669
|
+
if (!manifest)
|
|
670
|
+
return [];
|
|
671
|
+
const pending = [];
|
|
672
|
+
for (const key of MANIFEST_PACKAGE_KEYS) {
|
|
673
|
+
const value = key === 'description'
|
|
674
|
+
? localizedText(manifest.description, 'en')
|
|
675
|
+
: manifest[key];
|
|
676
|
+
const current = packageJson[key];
|
|
677
|
+
// ponytail: an author object ({name, email}) is left alone.
|
|
678
|
+
if (!value || typeof value !== 'string' || value === current)
|
|
679
|
+
continue;
|
|
680
|
+
if (current !== undefined && typeof current !== 'string')
|
|
681
|
+
continue;
|
|
682
|
+
pending.push({ key, from: current, value });
|
|
683
|
+
}
|
|
684
|
+
return pending;
|
|
685
|
+
}
|
|
571
686
|
/** What an unset field means, so spelling out the default is not a change. */
|
|
572
687
|
export const IMPLICIT_VALUES = {
|
|
573
688
|
authMethod: 'basic',
|
|
@@ -579,27 +694,38 @@ const display = (value) => typeof value === 'string' ? value : JSON.stringify(va
|
|
|
579
694
|
/** What syncing this directory would change in its package.json. */
|
|
580
695
|
export function getPackageJsonSyncChanges(dir) {
|
|
581
696
|
const current = readPackageDefaults(dir);
|
|
582
|
-
return
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
697
|
+
return [
|
|
698
|
+
...pendingSync(dir).map(({ key, value }) => current[key] === undefined
|
|
699
|
+
? { key: packageLabel(key), to: display(value) }
|
|
700
|
+
: {
|
|
701
|
+
key: packageLabel(key),
|
|
702
|
+
from: display(current[key]),
|
|
703
|
+
to: display(value),
|
|
704
|
+
}),
|
|
705
|
+
...pendingManifestSync(dir).map(({ key, from, value }) => ({
|
|
706
|
+
key,
|
|
707
|
+
...(from !== undefined && { from }),
|
|
708
|
+
to: value,
|
|
709
|
+
})),
|
|
710
|
+
];
|
|
589
711
|
}
|
|
590
712
|
export function hasPackageJson(dir) {
|
|
591
713
|
return fs.existsSync(path.join(dir, 'package.json'));
|
|
592
714
|
}
|
|
593
715
|
/**
|
|
594
|
-
* Copy the pending shared values from .dev_properties.json
|
|
595
|
-
*
|
|
716
|
+
* Copy the pending shared values from .dev_properties.json, and the manifest
|
|
717
|
+
* fields package.json mirrors, into package.json, creating package.json when
|
|
718
|
+
* the directory has none. Throws if it cannot be
|
|
596
719
|
* read or written.
|
|
597
720
|
*/
|
|
598
721
|
export function syncDevPropertiesToPackageJson(dir) {
|
|
599
722
|
const pending = pendingSync(dir);
|
|
600
|
-
|
|
723
|
+
const mirrored = pendingManifestSync(dir);
|
|
724
|
+
if (pending.length + mirrored.length === 0 && hasPackageJson(dir))
|
|
601
725
|
return false;
|
|
602
726
|
updatePackageJson(dir, packageJson => {
|
|
727
|
+
for (const { key, value } of mirrored)
|
|
728
|
+
packageJson[key] = value;
|
|
603
729
|
for (const { key, value } of pending) {
|
|
604
730
|
const topKey = PACKAGE_TOP_KEYS[key];
|
|
605
731
|
if (topKey) {
|
|
@@ -665,11 +791,13 @@ export function migrateLegacyPassword(project) {
|
|
|
665
791
|
}
|
|
666
792
|
}
|
|
667
793
|
catch {
|
|
668
|
-
// Unreadable file: nothing to migrate there.
|
|
794
|
+
// Unreadable file: nothing to migrate there. Unwritable: checked below.
|
|
669
795
|
}
|
|
670
796
|
}
|
|
671
|
-
|
|
672
|
-
|
|
797
|
+
// Moved only if no file still holds it.
|
|
798
|
+
const left = [project.root, ...ancestorDirs(project.root)].some(dir => readDevPropertiesFile(dir)?.password);
|
|
799
|
+
project.hasLegacyPassword = left;
|
|
800
|
+
return !left;
|
|
673
801
|
}
|
|
674
802
|
/**
|
|
675
803
|
* Ensure the dist directory exists
|
|
@@ -15,14 +15,15 @@ function domainRelated(a, b) {
|
|
|
15
15
|
* browser — so it's unit-testable.
|
|
16
16
|
*/
|
|
17
17
|
export function selectSessionCookie(all, siteDomain) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// Only a session on the deploy site counts: a Java IdP sets a JSESSIONID of
|
|
19
|
+
// its own, and that one must never be sent to the site.
|
|
20
|
+
const chosen = all.find(c => c.name === 'JSESSIONID' && domainRelated(c.domain, siteDomain));
|
|
21
|
+
if (!chosen) {
|
|
20
22
|
const domains = [...new Set(all.map(c => bareDomain(c.domain)))];
|
|
21
23
|
return {
|
|
22
|
-
error: `No JSESSIONID among ${all.length} cookies. Domains seen: ${domains.join(', ') || 'none'}. If these are only your IdP, open a Sitevision page in the browser, then press Enter again.`,
|
|
24
|
+
error: `No JSESSIONID for ${siteDomain} among ${all.length} cookies. Domains seen: ${domains.join(', ') || 'none'}. If these are only your IdP, open a Sitevision page in the browser, then press Enter again.`,
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
const chosen = sessions.find(c => domainRelated(c.domain, siteDomain)) ?? sessions[0];
|
|
26
27
|
const cookies = all.filter(c => domainRelated(c.domain, chosen.domain));
|
|
27
28
|
return {
|
|
28
29
|
cookie: cookies.map(c => `${c.name}=${c.value}`).join('; '),
|
|
@@ -127,6 +127,12 @@ export function makeRequest(url, options) {
|
|
|
127
127
|
headers: res.headers,
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
|
+
// A connection cut mid-body never emits 'end'.
|
|
131
|
+
res.on('error', reject);
|
|
132
|
+
res.on('close', () => {
|
|
133
|
+
if (!res.complete)
|
|
134
|
+
reject(new Error('Response was cut off'));
|
|
135
|
+
});
|
|
130
136
|
});
|
|
131
137
|
// Abort hung connections instead of blocking the CLI indefinitely.
|
|
132
138
|
req.setTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, () => {
|
|
@@ -424,23 +430,24 @@ export async function deployProduction(signedZipPath, config, appType) {
|
|
|
424
430
|
if (!deployResult.success) {
|
|
425
431
|
return deployResult;
|
|
426
432
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (!activationResult.success) {
|
|
431
|
-
return {
|
|
432
|
-
success: true,
|
|
433
|
-
executableId: deployResult.executableId,
|
|
434
|
-
message: `Deployed successfully but activation failed: ${activationResult.error}`,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
433
|
+
if (!config.activate)
|
|
434
|
+
return deployResult;
|
|
435
|
+
if (!deployResult.executableId) {
|
|
437
436
|
return {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
message: 'Deployed
|
|
437
|
+
...deployResult,
|
|
438
|
+
activated: false,
|
|
439
|
+
message: 'Deployed, but not activated: the server did not return an executable id.',
|
|
441
440
|
};
|
|
442
441
|
}
|
|
443
|
-
|
|
442
|
+
const activationResult = await activateApp(deployResult.executableId, config, appType);
|
|
443
|
+
return {
|
|
444
|
+
success: true,
|
|
445
|
+
executableId: deployResult.executableId,
|
|
446
|
+
activated: activationResult.success,
|
|
447
|
+
message: activationResult.success
|
|
448
|
+
? 'Deployed and activated successfully'
|
|
449
|
+
: `Deployed successfully but activation failed: ${activationResult.error}`,
|
|
450
|
+
};
|
|
444
451
|
}
|
|
445
452
|
// =============================================================================
|
|
446
453
|
// ADDON MANAGEMENT API
|
|
@@ -18,13 +18,32 @@
|
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import fs from 'fs';
|
|
20
20
|
import { killChild, spawnChild } from './process-runner.js';
|
|
21
|
+
import { getAppIdConfig, getFullAppId } from './project-detection.js';
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
+
* Directory of an installed package, looked up the way Node does: the project's
|
|
24
|
+
* node_modules, then each parent's, since workspaces hoist to the repo root.
|
|
25
|
+
*/
|
|
26
|
+
export function findInstalledPackage(projectRoot, name) {
|
|
27
|
+
let dir = path.resolve(projectRoot);
|
|
28
|
+
for (;;) {
|
|
29
|
+
const candidate = path.join(dir, 'node_modules', name);
|
|
30
|
+
if (fs.existsSync(candidate))
|
|
31
|
+
return candidate;
|
|
32
|
+
const parent = path.dirname(dir);
|
|
33
|
+
if (parent === dir)
|
|
34
|
+
return null;
|
|
35
|
+
dir = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const PACKAGE = '@sitevision/sitevision-scripts';
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the path to the sitevision-scripts CLI entry for a project.
|
|
23
41
|
* Returns null if the package is not installed.
|
|
24
42
|
*/
|
|
25
43
|
export function getSitevisionScriptsBin(projectRoot) {
|
|
26
|
-
const
|
|
27
|
-
|
|
44
|
+
const dir = findInstalledPackage(projectRoot, PACKAGE);
|
|
45
|
+
const bin = dir && path.join(dir, 'bin', 'sitevision-scripts.js');
|
|
46
|
+
return bin && fs.existsSync(bin) ? bin : null;
|
|
28
47
|
}
|
|
29
48
|
/**
|
|
30
49
|
* Whether the sitevision-scripts package is available in the project.
|
|
@@ -35,18 +54,12 @@ export function hasSitevisionScripts(projectRoot) {
|
|
|
35
54
|
/**
|
|
36
55
|
* Path of the zip that `sitevision-scripts build` writes.
|
|
37
56
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* builds the package is the one writing the file, so its convention is the source
|
|
42
|
-
* of truth — using `getZipPath` here would look for the wrong filename whenever a
|
|
43
|
-
* prefix/suffix is configured.
|
|
57
|
+
* sitevision-scripts names it from `APP_ID_PREFIX`/`APP_ID_SUFFIX`; the build is
|
|
58
|
+
* started with those set from `getAppIdConfig`, so this is the same file that
|
|
59
|
+
* sign and deploy look for.
|
|
44
60
|
*/
|
|
45
61
|
export function getDelegatedZipPath(projectRoot, manifestId) {
|
|
46
|
-
|
|
47
|
-
const suffix = process.env['APP_ID_SUFFIX'] ?? '';
|
|
48
|
-
const appId = `${prefix}${manifestId}${suffix}`;
|
|
49
|
-
return path.join(projectRoot, 'dist', `${appId}.zip`);
|
|
62
|
+
return path.join(projectRoot, 'dist', `${getFullAppId(manifestId)}.zip`);
|
|
50
63
|
}
|
|
51
64
|
// =============================================================================
|
|
52
65
|
// VERSION COMPATIBILITY
|
|
@@ -68,9 +81,11 @@ export const SUPPORTED_SITEVISION_SCRIPTS_RANGE = `>=${SUPPORTED_SITEVISION_SCRI
|
|
|
68
81
|
* is not installed / unreadable.
|
|
69
82
|
*/
|
|
70
83
|
export function getSitevisionScriptsVersion(projectRoot) {
|
|
71
|
-
const
|
|
84
|
+
const dir = findInstalledPackage(projectRoot, PACKAGE);
|
|
85
|
+
if (!dir)
|
|
86
|
+
return null;
|
|
72
87
|
try {
|
|
73
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
88
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
74
89
|
return parsed.version ?? null;
|
|
75
90
|
}
|
|
76
91
|
catch {
|
|
@@ -158,8 +173,10 @@ export async function runSitevisionScriptsBuild(projectRoot, onOutput, signal) {
|
|
|
158
173
|
}
|
|
159
174
|
return new Promise(resolve => {
|
|
160
175
|
let output = '';
|
|
176
|
+
const { prefix, suffix } = getAppIdConfig();
|
|
161
177
|
const child = spawnChild(process.execPath, [bin, 'build'], {
|
|
162
178
|
cwd: projectRoot,
|
|
179
|
+
env: { ...process.env, APP_ID_PREFIX: prefix, APP_ID_SUFFIX: suffix },
|
|
163
180
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
181
|
});
|
|
165
182
|
const abort = () => {
|