@loopress/cli 0.21.0 → 0.23.0

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.
Files changed (103) hide show
  1. package/README.md +457 -67
  2. package/dist/commands/acf/list.d.ts +2 -2
  3. package/dist/commands/acf/list.js +2 -5
  4. package/dist/commands/acf/pull.js +9 -22
  5. package/dist/commands/acf/push.js +3 -6
  6. package/dist/commands/api/list.js +2 -1
  7. package/dist/commands/api/publish.js +6 -22
  8. package/dist/commands/api/pull.js +12 -17
  9. package/dist/commands/api/push.js +7 -10
  10. package/dist/commands/app/list.d.ts +17 -0
  11. package/dist/commands/app/list.js +35 -0
  12. package/dist/commands/app/pull.d.ts +27 -0
  13. package/dist/commands/app/pull.js +94 -0
  14. package/dist/commands/app/push.d.ts +28 -0
  15. package/dist/commands/app/push.js +130 -0
  16. package/dist/commands/app/remove.d.ts +20 -0
  17. package/dist/commands/app/remove.js +42 -0
  18. package/dist/commands/composer/push.d.ts +1 -0
  19. package/dist/commands/composer/push.js +43 -19
  20. package/dist/commands/dev.js +1 -1
  21. package/dist/commands/doctor.js +2 -1
  22. package/dist/commands/form/list.d.ts +2 -4
  23. package/dist/commands/form/list.js +3 -10
  24. package/dist/commands/form/pull.js +15 -24
  25. package/dist/commands/form/push.js +17 -42
  26. package/dist/commands/init.js +1 -0
  27. package/dist/commands/login.js +6 -2
  28. package/dist/commands/logout.js +2 -1
  29. package/dist/commands/page/pull.js +17 -28
  30. package/dist/commands/page/push.js +27 -52
  31. package/dist/commands/plugin/add.d.ts +1 -0
  32. package/dist/commands/plugin/add.js +18 -8
  33. package/dist/commands/plugin/audit.d.ts +30 -0
  34. package/dist/commands/plugin/audit.js +138 -0
  35. package/dist/commands/plugin/pull.d.ts +1 -1
  36. package/dist/commands/plugin/pull.js +13 -15
  37. package/dist/commands/plugin/push.d.ts +14 -5
  38. package/dist/commands/plugin/push.js +171 -60
  39. package/dist/commands/plugin/status.d.ts +22 -0
  40. package/dist/commands/plugin/status.js +65 -0
  41. package/dist/commands/project/config.js +4 -1
  42. package/dist/commands/project/list.d.ts +16 -1
  43. package/dist/commands/project/list.js +22 -16
  44. package/dist/commands/project/pull.js +3 -2
  45. package/dist/commands/project/push.d.ts +5 -0
  46. package/dist/commands/project/push.js +79 -58
  47. package/dist/commands/promote.d.ts +18 -0
  48. package/dist/commands/promote.js +90 -0
  49. package/dist/commands/pull.d.ts +12 -0
  50. package/dist/commands/pull.js +51 -0
  51. package/dist/commands/push.d.ts +13 -0
  52. package/dist/commands/push.js +57 -0
  53. package/dist/commands/seo/list.d.ts +9 -2
  54. package/dist/commands/seo/list.js +9 -11
  55. package/dist/commands/seo/pull.js +14 -37
  56. package/dist/commands/seo/push.js +24 -48
  57. package/dist/commands/snippet/list.js +2 -1
  58. package/dist/commands/snippet/publish.js +5 -21
  59. package/dist/commands/snippet/pull.js +15 -20
  60. package/dist/commands/snippet/push.js +21 -30
  61. package/dist/commands/theme/add.d.ts +13 -0
  62. package/dist/commands/theme/add.js +35 -0
  63. package/dist/commands/theme/pull.d.ts +21 -0
  64. package/dist/commands/theme/pull.js +37 -0
  65. package/dist/commands/theme/push.d.ts +23 -0
  66. package/dist/commands/theme/push.js +130 -0
  67. package/dist/commands/theme/status.d.ts +20 -0
  68. package/dist/commands/theme/status.js +51 -0
  69. package/dist/commands/validate.d.ts +8 -0
  70. package/dist/commands/validate.js +24 -0
  71. package/dist/lib/app-manifest.d.ts +36 -0
  72. package/dist/lib/app-manifest.js +162 -0
  73. package/dist/lib/base.d.ts +10 -0
  74. package/dist/lib/base.js +56 -22
  75. package/dist/lib/guard-production-push.d.ts +12 -0
  76. package/dist/lib/guard-production-push.js +20 -0
  77. package/dist/lib/load-snippets.js +65 -60
  78. package/dist/lib/local-callback-server.js +9 -4
  79. package/dist/lib/push-command.d.ts +3 -0
  80. package/dist/lib/push-command.js +22 -15
  81. package/dist/lib/put-or-create.d.ts +13 -0
  82. package/dist/lib/put-or-create.js +19 -0
  83. package/dist/lib/readdir-tolerant.d.ts +5 -0
  84. package/dist/lib/readdir-tolerant.js +11 -0
  85. package/dist/lib/resolve-linked-project.d.ts +8 -0
  86. package/dist/lib/resolve-linked-project.js +27 -0
  87. package/dist/lib/validate-local.d.ts +10 -0
  88. package/dist/lib/validate-local.js +183 -0
  89. package/dist/types/plugin.d.ts +1 -0
  90. package/dist/types/project-config.generated.d.ts +11 -1
  91. package/dist/utils/plugin-sync.d.ts +26 -0
  92. package/dist/utils/plugin-sync.js +31 -0
  93. package/dist/utils/plugins.d.ts +15 -2
  94. package/dist/utils/plugins.js +57 -12
  95. package/dist/utils/pluralize.d.ts +1 -0
  96. package/dist/utils/pluralize.js +5 -0
  97. package/dist/utils/themes.d.ts +30 -0
  98. package/dist/utils/themes.js +29 -0
  99. package/dist/utils/version.d.ts +2 -0
  100. package/dist/utils/version.js +21 -0
  101. package/oclif.manifest.json +986 -125
  102. package/package.json +25 -20
  103. package/schemas/project-config.schema.json +22 -4
@@ -14,9 +14,18 @@ export function mergePluginManifest(existing, incoming) {
14
14
  }
15
15
  // WordPress core identifies each plugin by a `<folder>/<file>` id (or a bare `<file>` for a
16
16
  // single-file plugin) with the `.php` extension stripped; the WordPress.org slug is just the
17
- // folder name (or the bare id itself for a single-file plugin).
18
- function slugFromPluginFile(file) {
19
- return file.split('/', 1)[0];
17
+ // folder name (or the bare id itself for a single-file plugin). Composer + composer/installers
18
+ // installs a WPackagist plugin into `wp-content/plugins/<slug>/`, so folder name and slug stay
19
+ // aligned for anything Loopress manages.
20
+ //
21
+ // A single-file plugin's bare id isn't always its real slug though: Hello Dolly ships as
22
+ // `hello.php` but its WordPress.org (and WPackagist) slug is `hello-dolly`. When that happens,
23
+ // trust the plugin's own `Plugin URI` header over the guessed id.
24
+ const WP_ORG_PLUGIN_URI = /^https?:\/\/wordpress\.org\/plugins\/([^/]+)\/?$/;
25
+ function slugFromPluginFile(file, pluginUri) {
26
+ if (file.includes('/'))
27
+ return file.split('/', 1)[0];
28
+ return WP_ORG_PLUGIN_URI.exec(pluginUri)?.[1] ?? file;
20
29
  }
21
30
  export function parseInstalledPlugins(raw) {
22
31
  return raw
@@ -24,27 +33,63 @@ export function parseInstalledPlugins(raw) {
24
33
  active: item.status !== 'inactive',
25
34
  file: item.plugin,
26
35
  name: item.name,
27
- slug: slugFromPluginFile(item.plugin),
36
+ slug: slugFromPluginFile(item.plugin, item.plugin_uri),
28
37
  version: item.version,
29
38
  }))
30
39
  .filter((plugin) => !LOOPRESS_PLUGIN_SLUGS.has(plugin.slug));
31
40
  }
32
- export function diffPlugins(manifest, installed) {
41
+ export function diffPlugins(manifest, installed,
42
+ // Slugs Loopress currently manages, from the local composer.lock's wpackagist-plugin/*
43
+ // entries. Empty for a loopress.json-only project that doesn't keep a lockfile in the repo:
44
+ // `toRemove` then stays empty (removals still happen server-side, they just aren't previewed).
45
+ managedSlugs = new Set()) {
33
46
  const installedMap = new Map(installed.map((p) => [p.slug, p]));
34
47
  const toInstall = [];
48
+ const toPin = [];
35
49
  const toActivate = [];
36
- const upToDate = [];
37
- for (const slug of Object.keys(manifest)) {
50
+ const collisions = [];
51
+ const inSync = [];
52
+ for (const [slug, wanted] of Object.entries(manifest)) {
38
53
  const live = installedMap.get(slug);
39
54
  if (!live) {
40
- toInstall.push({ slug });
55
+ toInstall.push({ slug, version: wanted });
56
+ continue;
41
57
  }
42
- else if (live.active) {
43
- upToDate.push(slug);
58
+ // Installed, but Loopress doesn't own the folder yet: nothing else can be done with it
59
+ // until `--force` lets Composer take it over.
60
+ if (!managedSlugs.has(slug)) {
61
+ collisions.push({ installedVersion: live.version, slug });
62
+ continue;
44
63
  }
45
- else {
64
+ if (wanted !== 'latest' && live.version !== wanted) {
65
+ toPin.push({ from: live.version, slug, to: wanted });
66
+ continue;
67
+ }
68
+ if (!live.active) {
46
69
  toActivate.push({ file: live.file, slug });
70
+ continue;
47
71
  }
72
+ inSync.push(slug);
73
+ }
74
+ const toRemove = [...managedSlugs].filter((slug) => !Object.hasOwn(manifest, slug));
75
+ const untrackedActive = installed
76
+ .filter((p) => p.active && !Object.hasOwn(manifest, p.slug) && !managedSlugs.has(p.slug))
77
+ .map((p) => p.slug);
78
+ return { collisions, inSync, toActivate, toInstall, toPin, toRemove, untrackedActive };
79
+ }
80
+ // wpackagist-plugin/<slug> or wpackagist-theme/<slug> entries from a composer.lock string.
81
+ export function lockedWpackagistSlugs(composerLock, kind) {
82
+ if (!composerLock)
83
+ return new Set();
84
+ const prefix = `wpackagist-${kind}/`;
85
+ try {
86
+ const parsed = JSON.parse(composerLock);
87
+ return new Set((parsed.packages ?? [])
88
+ .map((p) => p.name ?? '')
89
+ .filter((name) => name.startsWith(prefix))
90
+ .map((name) => name.slice(prefix.length)));
91
+ }
92
+ catch {
93
+ return new Set();
48
94
  }
49
- return { toActivate, toInstall, upToDate };
50
95
  }
@@ -0,0 +1 @@
1
+ export declare function pluralize(count: number, singular: string, plural?: string): string;
@@ -0,0 +1,5 @@
1
+ // `${pluralize(files.length, 'form')}` renders "1 form" or "3 forms"; pass an explicit
2
+ // plural for irregular words (e.g. `pluralize(n, 'box', 'boxes')`).
3
+ export function pluralize(count, singular, plural = `${singular}s`) {
4
+ return `${count} ${count === 1 ? singular : plural}`;
5
+ }
@@ -0,0 +1,30 @@
1
+ export type WpNativeTheme = {
2
+ status: 'active' | 'inactive';
3
+ stylesheet: string;
4
+ version: string;
5
+ };
6
+ export type InstalledTheme = {
7
+ active: boolean;
8
+ slug: string;
9
+ version: string;
10
+ };
11
+ export type ThemeManifest = Record<string, string>;
12
+ export type ThemeDiff = {
13
+ collisions: Array<{
14
+ installedVersion: string;
15
+ slug: string;
16
+ }>;
17
+ inSync: string[];
18
+ toInstall: Array<{
19
+ slug: string;
20
+ version: string;
21
+ }>;
22
+ toPin: Array<{
23
+ from: string;
24
+ slug: string;
25
+ to: string;
26
+ }>;
27
+ toRemove: string[];
28
+ };
29
+ export declare function parseInstalledThemes(raw: WpNativeTheme[]): InstalledTheme[];
30
+ export declare function diffThemes(manifest: ThemeManifest, installed: InstalledTheme[], managedSlugs?: Set<string>): ThemeDiff;
@@ -0,0 +1,29 @@
1
+ // Themes are version-managed only: Loopress never switches the active theme (that can break a
2
+ // live site hard), so unlike plugins there is no activate / prune dimension here.
3
+ export function parseInstalledThemes(raw) {
4
+ return raw.map((t) => ({ active: t.status === 'active', slug: t.stylesheet, version: t.version }));
5
+ }
6
+ export function diffThemes(manifest, installed, managedSlugs = new Set()) {
7
+ const map = new Map(installed.map((t) => [t.slug, t]));
8
+ const toInstall = [];
9
+ const toPin = [];
10
+ const collisions = [];
11
+ const inSync = [];
12
+ for (const [slug, wanted] of Object.entries(manifest)) {
13
+ const live = map.get(slug);
14
+ if (!live) {
15
+ toInstall.push({ slug, version: wanted });
16
+ }
17
+ else if (!managedSlugs.has(slug)) {
18
+ collisions.push({ installedVersion: live.version, slug });
19
+ }
20
+ else if (wanted !== 'latest' && live.version !== wanted) {
21
+ toPin.push({ from: live.version, slug, to: wanted });
22
+ }
23
+ else {
24
+ inSync.push(slug);
25
+ }
26
+ }
27
+ const toRemove = [...managedSlugs].filter((slug) => !Object.hasOwn(manifest, slug));
28
+ return { collisions, inSync, toInstall, toPin, toRemove };
29
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isExactVersion(value: string): boolean;
2
+ export declare function compareVersions(a: string, b: string): null | number;
@@ -0,0 +1,21 @@
1
+ import { compareVersions as cmp, validate } from 'compare-versions';
2
+ // An exact version like 9.4.2 or 3.0.0-beta1, not a Composer constraint (^, ~, *, ranges,
3
+ // ||, x-wildcards). loopress.json pins an exact version or the literal "latest"; a constraint
4
+ // breaks the CLI's drift detection (it never string-equals the resolved version), so it is
5
+ // rejected at `add` time. Not `compare-versions`' `validate()`: that accepts "9.4.x".
6
+ //
7
+ // A prerelease suffix is only allowed once there are at least 3 numeric segments
8
+ // (major.minor.patch): compare-versions' own `validate()`, which compareVersions() below
9
+ // defers to, rejects a suffix on a 1- or 2-segment version (e.g. "0.9-beta"). Accepting that
10
+ // shape here would let it slip past `isExactVersion` at `add` time, then have
11
+ // `compareVersions`/`isDowngrade` silently treat it as unorderable, bypassing the --force
12
+ // gate on a real downgrade.
13
+ const EXACT_VERSION = /^\d+(\.\d+){0,3}$|^\d+(\.\d+){2,3}-[0-9A-Za-z.]+$/;
14
+ export function isExactVersion(value) {
15
+ return EXACT_VERSION.test(value);
16
+ }
17
+ // -1 / 0 / 1, or null when either side isn't a comparable version (e.g. "latest"). Wraps
18
+ // compare-versions so callers never have to guard against the throw it does on bad input.
19
+ export function compareVersions(a, b) {
20
+ return validate(a) && validate(b) ? cmp(a, b) : null;
21
+ }