sitevision-cli 1.0.0-beta.31 → 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.
@@ -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
- constructor(command: string, args?: string[], cwd?: string);
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
- constructor(command, args = [], cwd) {
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 into package.json,
173
- * creating package.json when the directory has none. Throws if it cannot be
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 pendingSync(dir).map(({ key, value }) => current[key] === undefined
583
- ? { key: packageLabel(key), to: display(value) }
584
- : {
585
- key: packageLabel(key),
586
- from: display(current[key]),
587
- to: display(value),
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 into package.json,
595
- * creating package.json when the directory has none. Throws if it cannot be
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
- if (pending.length === 0 && hasPackageJson(dir))
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) {
@@ -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 {};
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.0-beta.31",
3
+ "version": "1.0.0-beta.32",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"