@loopress/cli 0.32.2 → 0.33.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 +511 -154
- package/dist/commands/acf/list.d.ts +1 -1
- package/dist/commands/acf/list.js +3 -4
- package/dist/commands/acf/pull.d.ts +1 -1
- package/dist/commands/acf/pull.js +9 -6
- package/dist/commands/acf/push.d.ts +1 -1
- package/dist/commands/acf/push.js +5 -4
- package/dist/commands/api/publish.js +2 -1
- package/dist/commands/app/pull.js +2 -1
- package/dist/commands/app/push.js +5 -4
- package/dist/commands/app/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/app/{remove.js → rm.js} +2 -1
- package/dist/commands/block/diff.d.ts +2 -0
- package/dist/commands/block/diff.js +5 -0
- package/dist/commands/block/list.d.ts +8 -0
- package/dist/commands/block/list.js +23 -0
- package/dist/commands/block/pull.d.ts +21 -0
- package/dist/commands/block/pull.js +67 -0
- package/dist/commands/block/push.d.ts +20 -0
- package/dist/commands/block/push.js +48 -0
- package/dist/commands/block/rm.d.ts +23 -0
- package/dist/commands/block/rm.js +69 -0
- package/dist/commands/block/rollback.d.ts +2 -0
- package/dist/commands/block/rollback.js +5 -0
- package/dist/commands/composer/init.js +1 -0
- package/dist/commands/composer/push.js +5 -4
- package/dist/commands/content/copy.d.ts +3 -0
- package/dist/commands/content/copy.js +79 -2
- package/dist/commands/dev.js +4 -3
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.js +33 -3
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.js +37 -23
- package/dist/commands/form/pull.js +7 -5
- package/dist/commands/form/push.js +4 -3
- package/dist/commands/init.js +4 -3
- package/dist/commands/menu/push.js +8 -8
- package/dist/commands/option/list.js +5 -2
- package/dist/commands/option/push.js +2 -1
- package/dist/commands/option/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/option/{remove.js → rm.js} +2 -1
- package/dist/commands/page/push.js +2 -1
- package/dist/commands/plugin/audit.d.ts +4 -4
- package/dist/commands/plugin/audit.js +35 -15
- package/dist/commands/plugin/pull.js +3 -2
- package/dist/commands/plugin/push.js +2 -2
- package/dist/commands/plugin/status.js +3 -1
- package/dist/commands/project/config.js +7 -6
- package/dist/commands/project/pull.js +3 -1
- package/dist/commands/project/push.js +6 -4
- package/dist/commands/project/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/project/{remove.js → rm.js} +3 -2
- package/dist/commands/project/switch.js +6 -4
- package/dist/commands/promote.js +7 -2
- package/dist/commands/pull.d.ts +2 -0
- package/dist/commands/pull.js +42 -18
- package/dist/commands/push.d.ts +2 -1
- package/dist/commands/push.js +69 -25
- package/dist/commands/snippet/publish.js +9 -5
- package/dist/commands/snippet/push.d.ts +1 -1
- package/dist/commands/snippet/push.js +15 -12
- package/dist/commands/status.js +1 -0
- package/dist/commands/theme/activate.d.ts +21 -0
- package/dist/commands/theme/activate.js +44 -0
- package/dist/commands/theme/pull.js +2 -1
- package/dist/commands/theme/push.js +7 -3
- package/dist/commands/theme/style/push.js +7 -2
- package/dist/commands/theme/template/push.js +25 -6
- package/dist/config/auth.manager.d.ts +10 -7
- package/dist/config/auth.manager.js +5 -1
- package/dist/config/json-file.d.ts +1 -1
- package/dist/config/project-config.manager.d.ts +2 -2
- package/dist/config/project-config.manager.js +13 -21
- package/dist/lib/api-client.js +16 -21
- package/dist/lib/app-build.js +6 -2
- package/dist/lib/app-manifest.js +6 -6
- package/dist/lib/base.d.ts +2 -1
- package/dist/lib/base.js +10 -8
- package/dist/lib/bootstrap-full-install.js +8 -6
- package/dist/lib/capabilities.d.ts +44 -0
- package/dist/lib/capabilities.js +85 -0
- package/dist/lib/declared-type-command.js +4 -2
- package/dist/lib/dev-watch.js +5 -5
- package/dist/lib/diff-command.d.ts +2 -3
- package/dist/lib/diff-command.js +9 -7
- package/dist/lib/diff-state.js +2 -3
- package/dist/lib/load-files.js +2 -1
- package/dist/lib/load-snippets.js +37 -42
- package/dist/lib/local-callback-server.js +7 -1
- package/dist/lib/narrow.d.ts +5 -0
- package/dist/lib/narrow.js +26 -0
- package/dist/lib/php-files-command.js +8 -5
- package/dist/lib/plugin-detection.d.ts +0 -1
- package/dist/lib/plugin-detection.js +2 -4
- package/dist/lib/push-command.d.ts +1 -1
- package/dist/lib/push-command.js +10 -5
- package/dist/lib/put-or-create.d.ts +2 -2
- package/dist/lib/readdir-tolerant.js +3 -1
- package/dist/lib/resource-state.js +38 -13
- package/dist/lib/rollback-command.d.ts +1 -1
- package/dist/lib/rollback-command.js +3 -2
- package/dist/lib/rollback-materialize.js +28 -8
- package/dist/lib/rotate-app-password.js +1 -0
- package/dist/lib/sentry.js +2 -2
- package/dist/lib/snapshot-store.d.ts +10 -7
- package/dist/lib/snapshot-store.js +21 -5
- package/dist/lib/spinner.js +4 -0
- package/dist/lib/validate-local.js +35 -20
- package/dist/lib/wp-client.d.ts +6 -1
- package/dist/lib/wp-client.js +54 -69
- package/dist/lib/wp-site-diagnostic.js +3 -5
- package/dist/types/global-config.generated.d.ts +1 -1
- package/dist/types/plugin.d.ts +7 -5
- package/dist/types/plugin.js +6 -1
- package/dist/types/project-config.generated.d.ts +9 -1
- package/dist/types/snippet.d.ts +2 -2
- package/dist/types/snippet.js +1 -1
- package/dist/utils/acf-format.js +6 -3
- package/dist/utils/block-format.d.ts +27 -0
- package/dist/utils/block-format.js +93 -0
- package/dist/utils/composer.d.ts +7 -15
- package/dist/utils/composer.js +43 -16
- package/dist/utils/declared-type-format.js +26 -19
- package/dist/utils/form-format.js +2 -2
- package/dist/utils/loopress-config.js +9 -1
- package/dist/utils/menu-format.d.ts +3 -1
- package/dist/utils/menu-format.js +5 -0
- package/dist/utils/option-format.d.ts +2 -2
- package/dist/utils/option-format.js +15 -25
- package/dist/utils/page-format.js +7 -6
- package/dist/utils/php-top-level.js +15 -4
- package/dist/utils/plugin-sync.d.ts +7 -7
- package/dist/utils/plugin-sync.js +11 -11
- package/dist/utils/plugins.js +9 -6
- package/dist/utils/resource-dirs.d.ts +2 -0
- package/dist/utils/resource-dirs.js +9 -1
- package/dist/utils/snippet-format.d.ts +1 -1
- package/dist/utils/snippet-format.js +5 -3
- package/dist/utils/template-format.js +2 -2
- package/dist/utils/theme-css.js +14 -6
- package/dist/utils/theme-styles-format.d.ts +3 -0
- package/dist/utils/theme-styles-format.js +15 -5
- package/dist/utils/version.js +1 -1
- package/oclif.manifest.json +1938 -1393
- package/package.json +42 -17
- package/schemas/global-config.schema.json +1 -1
- package/schemas/project-config.schema.json +11 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
export declare const SYNC_TIMEOUT_MS = 600000;
|
|
2
3
|
export type SyncIntent = {
|
|
3
4
|
libraries?: Record<string, string>;
|
|
@@ -20,13 +21,12 @@ export type SyncResponse = {
|
|
|
20
21
|
lockDrift?: LockDriftEntry[];
|
|
21
22
|
message: string;
|
|
22
23
|
output: string;
|
|
23
|
-
removed
|
|
24
|
-
};
|
|
25
|
-
export type Collision = {
|
|
26
|
-
installedVersion: string;
|
|
27
|
-
path: string;
|
|
28
|
-
slug: string;
|
|
29
|
-
type: 'plugin' | 'theme';
|
|
24
|
+
removed?: string[];
|
|
30
25
|
};
|
|
26
|
+
declare const COLLISIONS: z.ZodArray<z.ZodObject<{
|
|
27
|
+
slug: z.ZodString;
|
|
28
|
+
}, z.core.$loose>>;
|
|
29
|
+
export type Collision = z.infer<typeof COLLISIONS>[number];
|
|
31
30
|
export declare function parseCollisions(error: unknown): Collision[] | null;
|
|
32
31
|
export declare function isDowngrade(from: string, to: string): boolean;
|
|
32
|
+
export {};
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
// `lps plugin push`, `lps theme version push` and `lps composer push`. The endpoint runs Composer +
|
|
3
3
|
// WPackagist inside WordPress; the CLI only ever sends intent (which slugs at which versions),
|
|
4
4
|
// never a composer.json, so the plugin stays the single authority on that file's shape.
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { causeOf } from '../lib/narrow.js';
|
|
7
|
+
import { parseBody, responseOf } from '../lib/wp-client.js';
|
|
5
8
|
import { compareVersions } from './version.js';
|
|
6
9
|
// A cold `composer update` on a fresh site easily exceeds the global 30s WpClient timeout.
|
|
7
10
|
export const SYNC_TIMEOUT_MS = 600_000;
|
|
11
|
+
// The server also sends installedVersion, path and type, only the slug is read here.
|
|
12
|
+
const COLLISIONS = z.array(z.looseObject({ slug: z.string() }));
|
|
8
13
|
// 422 from the sync endpoint when the intent references a plugin/theme whose folder already
|
|
9
14
|
// exists on the site but isn't tracked in composer.lock (installed by hand through wp-admin).
|
|
10
15
|
// Re-running with --force lets the server remove the folder so Composer can take it over.
|
|
11
16
|
export function parseCollisions(error) {
|
|
12
|
-
const body = error
|
|
13
|
-
if (
|
|
17
|
+
const { body, statusCode } = responseOf(causeOf(error));
|
|
18
|
+
if (statusCode !== 422)
|
|
14
19
|
return null;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (parsed.error === 'unmanaged_plugins_present' && Array.isArray(parsed.collisions)) {
|
|
18
|
-
return parsed.collisions;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
20
|
+
const parsed = parseBody(body);
|
|
21
|
+
if (parsed.error !== 'unmanaged_plugins_present')
|
|
22
22
|
return null;
|
|
23
|
-
|
|
24
|
-
return null;
|
|
23
|
+
const collisions = COLLISIONS.safeParse(parsed.collisions);
|
|
24
|
+
return collisions.success ? collisions.data : null;
|
|
25
25
|
}
|
|
26
26
|
// Only gates whether a version change needs --force. Unorderable inputs ("latest", a
|
|
27
27
|
// constraint) count as "not a downgrade" so they don't block the push.
|
package/dist/utils/plugins.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../lib/narrow.js';
|
|
1
2
|
import { pinActive, pinVersion, } from '../types/plugin.js';
|
|
2
3
|
import { isExactVersion } from './version.js';
|
|
3
4
|
// "1.7.2", or "1.7.2 (inactive)" for a pin that keeps the plugin inactive.
|
|
@@ -13,9 +14,10 @@ const LOOPRESS_PLUGIN_SLUGS = new Set(['loopress', 'loopress-full', 'loopress-li
|
|
|
13
14
|
export function mergePluginManifest(existing, incoming) {
|
|
14
15
|
const merged = { ...existing, ...incoming };
|
|
15
16
|
const added = Object.keys(incoming).filter((s) => !Object.hasOwn(existing, s));
|
|
16
|
-
const updated = Object.
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const updated = Object.entries(incoming).flatMap(([slug, pin]) => {
|
|
18
|
+
const before = Object.hasOwn(existing, slug) ? existing[slug] : undefined;
|
|
19
|
+
return before !== undefined && describePin(before) !== describePin(pin) ? [{ from: describePin(before), slug, to: describePin(pin) }] : [];
|
|
20
|
+
});
|
|
19
21
|
return { added, merged, updated };
|
|
20
22
|
}
|
|
21
23
|
// WordPress core identifies each plugin by a `<folder>/<file>` id (or a bare `<file>` for a
|
|
@@ -30,7 +32,7 @@ export function mergePluginManifest(existing, incoming) {
|
|
|
30
32
|
const WP_ORG_PLUGIN_URI = /^https?:\/\/wordpress\.org\/plugins\/([^/]+)\/?$/;
|
|
31
33
|
function slugFromPluginFile(file, pluginUri) {
|
|
32
34
|
if (file.includes('/'))
|
|
33
|
-
return file.
|
|
35
|
+
return file.slice(0, file.indexOf('/'));
|
|
34
36
|
return WP_ORG_PLUGIN_URI.exec(pluginUri)?.[1] ?? file;
|
|
35
37
|
}
|
|
36
38
|
export function parseInstalledPlugins(raw) {
|
|
@@ -93,8 +95,9 @@ export function lockedWpackagistSlugs(composerLock, kind) {
|
|
|
93
95
|
const prefix = `wpackagist-${kind}/`;
|
|
94
96
|
try {
|
|
95
97
|
const parsed = JSON.parse(composerLock);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
const packages = isRecord(parsed) && Array.isArray(parsed.packages) ? parsed.packages : [];
|
|
99
|
+
return new Set(packages
|
|
100
|
+
.map((p) => (isRecord(p) && typeof p.name === 'string' ? p.name : ''))
|
|
98
101
|
.filter((name) => name.startsWith(prefix))
|
|
99
102
|
.map((name) => name.slice(prefix.length)));
|
|
100
103
|
}
|
|
@@ -3,6 +3,7 @@ export declare const RESOURCE_DIR_DEFAULTS: {
|
|
|
3
3
|
readonly acf: "acf";
|
|
4
4
|
readonly api: "api";
|
|
5
5
|
readonly apps: "apps";
|
|
6
|
+
readonly block: "blocks";
|
|
6
7
|
readonly blockCss: "theme/blocks";
|
|
7
8
|
readonly cpt: "cpt";
|
|
8
9
|
readonly css: "theme/css";
|
|
@@ -23,3 +24,4 @@ export declare const RESOURCE_DIR_DEFAULTS: {
|
|
|
23
24
|
export type ResourceDirKind = keyof typeof RESOURCE_DIR_DEFAULTS;
|
|
24
25
|
export declare function resolveResourceDir(kind: ResourceDirKind, localConfig: LoopressLocalConfig, override?: string): string;
|
|
25
26
|
export declare function projectUses(kind: ResourceDirKind, localConfig: LoopressLocalConfig): boolean;
|
|
27
|
+
export declare function projectHasChildThemeFiles(localConfig: LoopressLocalConfig): boolean;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
+
import {} from './loopress-config.js';
|
|
3
4
|
// The default subdirectory for each file-backed resource, relative to rootDir. Used whenever
|
|
4
5
|
// loopress.json has no `<kind>Dir` key, which is the case for a fresh `lps init`.
|
|
5
6
|
export const RESOURCE_DIR_DEFAULTS = {
|
|
6
7
|
acf: 'acf',
|
|
7
8
|
api: 'api',
|
|
8
9
|
apps: 'apps',
|
|
10
|
+
block: 'blocks',
|
|
9
11
|
blockCss: 'theme/blocks',
|
|
10
12
|
cpt: 'cpt',
|
|
11
13
|
css: 'theme/css',
|
|
@@ -27,6 +29,7 @@ const CONFIG_KEY = {
|
|
|
27
29
|
acf: 'acfDir',
|
|
28
30
|
api: 'apiDir',
|
|
29
31
|
apps: 'appsDir',
|
|
32
|
+
block: 'blockDir',
|
|
30
33
|
blockCss: 'blockCssDir',
|
|
31
34
|
cpt: 'cptDir',
|
|
32
35
|
css: 'cssDir',
|
|
@@ -53,10 +56,15 @@ export function resolveResourceDir(kind, localConfig, override) {
|
|
|
53
56
|
return override;
|
|
54
57
|
const rootDir = localConfig.rootDir ?? '.';
|
|
55
58
|
const configured = localConfig[CONFIG_KEY[kind]];
|
|
56
|
-
return join(rootDir, configured
|
|
59
|
+
return join(rootDir, typeof configured === 'string' ? configured : RESOURCE_DIR_DEFAULTS[kind]);
|
|
57
60
|
}
|
|
58
61
|
// Whether the project uses a resource at all: its directory exists. What the aggregate `lps push`,
|
|
59
62
|
// `lps pull` and `lps diff` go by for a resource backed by a plugin the site may not have.
|
|
60
63
|
export function projectUses(kind, localConfig) {
|
|
61
64
|
return existsSync(resolve(resolveResourceDir(kind, localConfig)));
|
|
62
65
|
}
|
|
66
|
+
// The project has files for the Loopress child theme to hold: templates, parts or CSS. Without
|
|
67
|
+
// them the child would be empty, activateChildTheme or not: `lps theme activate` covers that.
|
|
68
|
+
export function projectHasChildThemeFiles(localConfig) {
|
|
69
|
+
return ['template', 'part', 'css', 'blockCss'].some((kind) => projectUses(kind, localConfig));
|
|
70
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isOneOf } from '../lib/narrow.js';
|
|
2
|
+
import {} from '../types/snippet.generated.js';
|
|
1
3
|
export const SNIPPETS_ENDPOINT = 'loopress/v1/snippets';
|
|
2
4
|
// Coerces server JSON that should be a string but, being unknown, could in principle be an
|
|
3
5
|
// object; a plain String() on that would print "[object Object]" instead of falling back.
|
|
@@ -7,12 +9,12 @@ function coerceString(value, fallback = '') {
|
|
|
7
9
|
export function parseType(raw) {
|
|
8
10
|
const valid = ['css', 'html', 'js', 'php', 'text'];
|
|
9
11
|
const value = coerceString(raw).toLowerCase();
|
|
10
|
-
return valid
|
|
12
|
+
return isOneOf(valid, value) ? value : null;
|
|
11
13
|
}
|
|
12
14
|
const VALID_LOCATIONS = new Set(['admin', 'body', 'everywhere', 'footer', 'frontend', 'header', 'once']);
|
|
13
15
|
export function parseLocation(raw) {
|
|
14
16
|
const value = coerceString(raw).toLowerCase();
|
|
15
|
-
return VALID_LOCATIONS
|
|
17
|
+
return isOneOf(VALID_LOCATIONS, value) ? value : null;
|
|
16
18
|
}
|
|
17
19
|
export function parseInsertMethod(raw) {
|
|
18
20
|
return raw === 'auto' || raw === 'shortcode' ? raw : null;
|
|
@@ -83,7 +85,7 @@ export function normalizeSnippet(data) {
|
|
|
83
85
|
priority: resolvePriority(data.priority),
|
|
84
86
|
revision: typeof data.revision === 'string' ? data.revision : undefined,
|
|
85
87
|
shortcodeAttributes: Array.isArray(data.shortcodeAttributes) ? data.shortcodeAttributes.map(String) : [],
|
|
86
|
-
tags: Array.isArray(data.tags) ? data.tags : [],
|
|
88
|
+
tags: Array.isArray(data.tags) ? data.tags.map(String) : [],
|
|
87
89
|
type: parseType(data.type) ?? 'php',
|
|
88
90
|
};
|
|
89
91
|
}
|
|
@@ -32,8 +32,8 @@ export function parseTemplateFile(slug, raw) {
|
|
|
32
32
|
// `area` defaults from the name, the way themes name their parts: header, header-large-title...
|
|
33
33
|
export function parsePartFile(slug, raw) {
|
|
34
34
|
const { body, meta } = parseFile(raw, ['title', 'area']);
|
|
35
|
-
const nameArea = /^(header|footer)(
|
|
36
|
-
return { area: meta.area || nameArea, html: body, slug, title: meta.title || titleFromSlug(slug) };
|
|
35
|
+
const nameArea = /^(header|footer)(?:-|$)/.exec(slug)?.[1] ?? 'uncategorized';
|
|
36
|
+
return { area: (meta.area ?? '') || nameArea, html: body, slug, title: (meta.title ?? '') || titleFromSlug(slug) };
|
|
37
37
|
}
|
|
38
38
|
// Same directory rules as pages/ (flat, `.html` only), with template slugs, every problem at once.
|
|
39
39
|
export async function readLocalTemplates(templatesDir, partsDir) {
|
package/dist/utils/theme-css.js
CHANGED
|
@@ -3,6 +3,9 @@ import { Buffer } from 'node:buffer';
|
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { readdir } from 'node:fs/promises';
|
|
5
5
|
import { extname, join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { errorCode } from '../lib/narrow.js';
|
|
8
|
+
import {} from './page-format.js';
|
|
6
9
|
// The global stylesheet's entry: it @imports the rest of theme/css/ in cascade order.
|
|
7
10
|
export const STYLE_ENTRY = 'style.css';
|
|
8
11
|
// WordPress's own block name rule (register_block_type): lowercase letters, digits and dashes,
|
|
@@ -18,7 +21,7 @@ async function listFiles(dir) {
|
|
|
18
21
|
.toSorted((a, b) => a.localeCompare(b));
|
|
19
22
|
}
|
|
20
23
|
catch (error) {
|
|
21
|
-
if (error
|
|
24
|
+
if (errorCode(error) === 'ENOENT')
|
|
22
25
|
return [];
|
|
23
26
|
throw error;
|
|
24
27
|
}
|
|
@@ -27,8 +30,9 @@ async function readBlockEntries(blocksDir, problems) {
|
|
|
27
30
|
const entries = [];
|
|
28
31
|
for (const file of await listFiles(blocksDir)) {
|
|
29
32
|
const segments = relative(blocksDir, file).split(sep);
|
|
30
|
-
const [namespace,
|
|
31
|
-
|
|
33
|
+
const [namespace, fileName] = segments;
|
|
34
|
+
const block = fileName?.replace(/\.css$/, '');
|
|
35
|
+
if (namespace === undefined || block === undefined || segments.length !== 2 || extname(file) !== '.css' || !BLOCK_SEGMENT.test(namespace) || !BLOCK_SEGMENT.test(block)) {
|
|
32
36
|
problems.push({ file, message: `must be <namespace>/<block>.css, named after a block (e.g. ${join(blocksDir, 'core', 'button.css')})` });
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
@@ -36,11 +40,15 @@ async function readBlockEntries(blocksDir, problems) {
|
|
|
36
40
|
}
|
|
37
41
|
return entries;
|
|
38
42
|
}
|
|
43
|
+
const BUILD_LOCATION = z.object({ file: z.string(), line: z.number() });
|
|
44
|
+
const BUILD_MESSAGE = z.object({ location: BUILD_LOCATION.nullable(), text: z.string() });
|
|
45
|
+
const BUILD_FAILURE = z.object({ errors: z.array(BUILD_MESSAGE) });
|
|
39
46
|
function buildProblems(error) {
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
// esbuild's BuildFailure, the only part of it read here. Anything else is not a CSS problem.
|
|
48
|
+
const failure = BUILD_FAILURE.safeParse(error);
|
|
49
|
+
if (!failure.success)
|
|
42
50
|
throw error;
|
|
43
|
-
return errors.map((message) => ({
|
|
51
|
+
return failure.data.errors.map((message) => ({
|
|
44
52
|
file: message.location ? `${message.location.file}:${message.location.line}` : 'esbuild',
|
|
45
53
|
message: message.text,
|
|
46
54
|
}));
|
|
@@ -8,8 +8,11 @@ export type WpThemeSupportInfo = {
|
|
|
8
8
|
}>>;
|
|
9
9
|
status: 'active' | 'inactive';
|
|
10
10
|
stylesheet: string;
|
|
11
|
+
template?: string;
|
|
11
12
|
theme_supports?: Record<string, unknown>;
|
|
12
13
|
};
|
|
14
|
+
export declare const CHILD_THEME_SUFFIX = "-loopress";
|
|
15
|
+
export declare function projectStylesheet(theme: Pick<WpThemeSupportInfo, 'stylesheet' | 'template'>): string;
|
|
13
16
|
export declare function isBlockTheme(theme: Pick<WpThemeSupportInfo, 'theme_supports'>): boolean;
|
|
14
17
|
export declare function getUserGlobalStylesId(theme: Pick<WpThemeSupportInfo, '_links'>): null | number;
|
|
15
18
|
export declare function globalStylesFiles(dir: string): Promise<string[]>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { readdirTolerant } from '../lib/readdir-tolerant.js';
|
|
3
|
+
import {} from '../lib/wp-client.js';
|
|
3
4
|
// Global Styles are the user's customizations from the Site Editor's "Styles" screen (colors,
|
|
4
5
|
// typography, spacing), stored by WordPress core in a `wp_global_styles` post, one per theme.
|
|
5
6
|
// This is separate from the theme's own theme.json file, which lives in the theme's repo and is
|
|
@@ -11,6 +12,15 @@ export function themeStylesFileName(stylesheet) {
|
|
|
11
12
|
export function globalStylesEndpoint(id) {
|
|
12
13
|
return `wp/v2/global-styles/${id}`;
|
|
13
14
|
}
|
|
15
|
+
// Appended to the parent's stylesheet by the plugin's ChildThemeController.
|
|
16
|
+
export const CHILD_THEME_SUFFIX = '-loopress';
|
|
17
|
+
// The stylesheet a project's Global Styles file is named after: the parent's when the Loopress
|
|
18
|
+
// child is active, so the tracked file stays the same whether or not the child was activated
|
|
19
|
+
// (the plugin copies the parent's Global Styles into the child when activating it).
|
|
20
|
+
export function projectStylesheet(theme) {
|
|
21
|
+
const { stylesheet, template } = theme;
|
|
22
|
+
return template && stylesheet === `${template}${CHILD_THEME_SUFFIX}` ? template : stylesheet;
|
|
23
|
+
}
|
|
14
24
|
// WordPress core surfaces `wp_is_block_theme()` over the `wp/v2/themes` REST response as
|
|
15
25
|
// `theme_supports['block-templates']` (see `_add_default_theme_supports()` in
|
|
16
26
|
// wp-includes/theme.php): core adds that feature flag itself exactly when the theme has its own
|
|
@@ -56,13 +66,13 @@ export async function globalStylesSkipNote(wp, dir, action) {
|
|
|
56
66
|
const active = await getActiveTheme(wp);
|
|
57
67
|
if (!isBlockTheme(active))
|
|
58
68
|
return `the active theme "${active.stylesheet}" is a classic theme, skipped`;
|
|
59
|
-
if (action === 'push' && !files.includes(themeStylesFileName(active
|
|
60
|
-
return `no file for the active theme "${active
|
|
69
|
+
if (action === 'push' && !files.includes(themeStylesFileName(projectStylesheet(active)))) {
|
|
70
|
+
return `no file for the active theme "${projectStylesheet(active)}", skipped`;
|
|
61
71
|
}
|
|
62
72
|
return undefined;
|
|
63
73
|
}
|
|
64
|
-
// Resolves the active theme's stylesheet slug
|
|
65
|
-
// one call, failing clearly (rather than with a raw REST error later) when the active theme
|
|
74
|
+
// Resolves the active theme's stylesheet slug, as the project names it (see projectStylesheet),
|
|
75
|
+
// and the id of its editable Global Styles post in one call, failing clearly (rather than with a raw REST error later) when the active theme
|
|
66
76
|
// isn't a block theme: classic themes have no Styles screen and no `wp_global_styles` post at all.
|
|
67
77
|
export async function getActiveThemeGlobalStyles(wp) {
|
|
68
78
|
const active = await getActiveTheme(wp);
|
|
@@ -73,7 +83,7 @@ export async function getActiveThemeGlobalStyles(wp) {
|
|
|
73
83
|
if (id === null) {
|
|
74
84
|
throw new Error(`Could not resolve the editable Global Styles post for "${active.stylesheet}". Requires an admin-capable account (edit_theme_options) on WordPress 5.9+.`);
|
|
75
85
|
}
|
|
76
|
-
return { id, stylesheet: active
|
|
86
|
+
return { id, stylesheet: projectStylesheet(active) };
|
|
77
87
|
}
|
|
78
88
|
// The compared/stored value: `settings` and `styles` only. `id` and `_links` are WordPress
|
|
79
89
|
// bookkeeping, not part of the tracked configuration (a fresh install resolves to a different
|
package/dist/utils/version.js
CHANGED
|
@@ -10,7 +10,7 @@ import { compareVersions as cmp, validate } from 'compare-versions';
|
|
|
10
10
|
// shape here would let it slip past `isExactVersion` at `add` time, then have
|
|
11
11
|
// `compareVersions`/`isDowngrade` silently treat it as unorderable, bypassing the --force
|
|
12
12
|
// gate on a real downgrade.
|
|
13
|
-
const EXACT_VERSION = /^\d+(
|
|
13
|
+
const EXACT_VERSION = /^\d+(?:\.\d+){0,3}$|^\d+(?:\.\d+){2,3}-[\d.a-z]+$/i;
|
|
14
14
|
export function isExactVersion(value) {
|
|
15
15
|
return EXACT_VERSION.test(value);
|
|
16
16
|
}
|