@loopress/cli 0.22.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.
- package/README.md +335 -58
- package/dist/commands/app/list.d.ts +17 -0
- package/dist/commands/app/list.js +35 -0
- package/dist/commands/app/pull.d.ts +27 -0
- package/dist/commands/app/pull.js +94 -0
- package/dist/commands/app/push.d.ts +28 -0
- package/dist/commands/app/push.js +130 -0
- package/dist/commands/app/remove.d.ts +20 -0
- package/dist/commands/app/remove.js +42 -0
- package/dist/commands/composer/push.d.ts +1 -0
- package/dist/commands/composer/push.js +43 -19
- package/dist/commands/init.js +1 -0
- package/dist/commands/plugin/add.d.ts +1 -0
- package/dist/commands/plugin/add.js +18 -8
- package/dist/commands/plugin/audit.d.ts +30 -0
- package/dist/commands/plugin/audit.js +138 -0
- package/dist/commands/plugin/pull.d.ts +1 -2
- package/dist/commands/plugin/pull.js +11 -11
- package/dist/commands/plugin/push.d.ts +14 -8
- package/dist/commands/plugin/push.js +168 -67
- package/dist/commands/plugin/status.d.ts +22 -0
- package/dist/commands/plugin/status.js +65 -0
- package/dist/commands/theme/add.d.ts +13 -0
- package/dist/commands/theme/add.js +35 -0
- package/dist/commands/theme/pull.d.ts +21 -0
- package/dist/commands/theme/pull.js +37 -0
- package/dist/commands/theme/push.d.ts +23 -0
- package/dist/commands/theme/push.js +130 -0
- package/dist/commands/theme/status.d.ts +20 -0
- package/dist/commands/theme/status.js +51 -0
- package/dist/lib/app-manifest.d.ts +36 -0
- package/dist/lib/app-manifest.js +162 -0
- package/dist/lib/base.d.ts +1 -0
- package/dist/lib/base.js +5 -0
- package/dist/types/plugin.d.ts +1 -0
- package/dist/types/project-config.generated.d.ts +11 -1
- package/dist/utils/plugin-sync.d.ts +26 -0
- package/dist/utils/plugin-sync.js +31 -0
- package/dist/utils/plugins.d.ts +15 -2
- package/dist/utils/plugins.js +57 -12
- package/dist/utils/themes.d.ts +30 -0
- package/dist/utils/themes.js +29 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.js +21 -0
- package/oclif.manifest.json +714 -68
- package/package.json +22 -18
- package/schemas/project-config.schema.json +22 -4
package/dist/utils/plugins.js
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
|
37
|
-
|
|
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
|
|
43
|
-
|
|
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
|
-
|
|
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,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,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
|
+
}
|