@loopress/cli 0.18.0 → 0.19.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 +138 -74
- package/dist/commands/acf/pull.d.ts +1 -1
- package/dist/commands/acf/pull.js +12 -25
- package/dist/commands/acf/push.d.ts +1 -1
- package/dist/commands/acf/push.js +18 -47
- package/dist/commands/api/pull.d.ts +1 -1
- package/dist/commands/api/pull.js +11 -26
- package/dist/commands/api/push.d.ts +1 -1
- package/dist/commands/api/push.js +21 -27
- package/dist/commands/composer/init.js +11 -1
- package/dist/commands/composer/push.d.ts +1 -0
- package/dist/commands/composer/push.js +17 -4
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +79 -0
- package/dist/commands/form/pull.d.ts +1 -2
- package/dist/commands/form/pull.js +13 -32
- package/dist/commands/form/push.d.ts +1 -1
- package/dist/commands/form/push.js +6 -14
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +64 -33
- package/dist/commands/plugin/push.d.ts +1 -1
- package/dist/commands/plugin/push.js +2 -14
- package/dist/commands/project/config.js +4 -0
- package/dist/commands/project/push.d.ts +5 -0
- package/dist/commands/project/push.js +21 -11
- package/dist/commands/seo/pull.d.ts +1 -3
- package/dist/commands/seo/pull.js +15 -43
- package/dist/commands/seo/push.d.ts +1 -1
- package/dist/commands/seo/push.js +4 -17
- package/dist/commands/snippet/publish.js +2 -2
- package/dist/commands/snippet/pull.d.ts +1 -4
- package/dist/commands/snippet/pull.js +12 -60
- package/dist/commands/snippet/push.d.ts +1 -1
- package/dist/commands/snippet/push.js +4 -13
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.js +25 -4
- package/dist/config/project-config.manager.js +2 -2
- package/dist/lib/api-client.d.ts +2 -1
- package/dist/lib/api-client.js +10 -3
- package/dist/lib/base.d.ts +9 -0
- package/dist/lib/base.js +61 -2
- package/dist/lib/find-orphaned-files.d.ts +7 -0
- package/dist/lib/find-orphaned-files.js +29 -0
- package/dist/lib/interactive.d.ts +1 -0
- package/dist/lib/interactive.js +7 -0
- package/dist/lib/load-files.d.ts +5 -0
- package/dist/lib/load-files.js +31 -0
- package/dist/lib/push-command.d.ts +7 -0
- package/dist/lib/push-command.js +40 -2
- package/dist/lib/wp-client.d.ts +9 -4
- package/dist/lib/wp-client.js +26 -12
- package/dist/utils/composer.d.ts +3 -0
- package/dist/utils/seo-format.d.ts +1 -0
- package/dist/utils/seo-format.js +5 -0
- package/dist/utils/snippet-format.d.ts +2 -0
- package/dist/utils/snippet-format.js +27 -1
- package/dist/utils/to-slug.d.ts +1 -0
- package/dist/utils/to-slug.js +7 -0
- package/oclif.manifest.json +469 -61
- package/package.json +10 -4
package/dist/lib/wp-client.js
CHANGED
|
@@ -15,22 +15,23 @@ export class WpClient {
|
|
|
15
15
|
timeout: { request: REQUEST_TIMEOUT_MS },
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
async get(path) {
|
|
19
|
-
return this.request('get', path);
|
|
18
|
+
async get(path, options) {
|
|
19
|
+
return this.request('get', path, undefined, options);
|
|
20
20
|
}
|
|
21
|
-
async post(path, json) {
|
|
22
|
-
return this.request('post', path, json);
|
|
21
|
+
async post(path, json, options) {
|
|
22
|
+
return this.request('post', path, json, options);
|
|
23
23
|
}
|
|
24
|
-
async put(path, json) {
|
|
25
|
-
return this.request('put', path, json);
|
|
24
|
+
async put(path, json, options) {
|
|
25
|
+
return this.request('put', path, json, options);
|
|
26
26
|
}
|
|
27
|
-
async request(method, path, json) {
|
|
27
|
+
async request(method, path, json, options) {
|
|
28
|
+
const timeoutMs = options?.timeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
28
29
|
try {
|
|
29
|
-
const response = await this.client(path, { json, method });
|
|
30
|
+
const response = await this.client(path, { json, method, timeout: { request: timeoutMs } });
|
|
30
31
|
return (response.body ? JSON.parse(response.body) : undefined);
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
33
|
-
throw new Error(formatWpError(error, `${this.siteUrl}/wp-json/${path}
|
|
34
|
+
throw new Error(formatWpError(error, `${this.siteUrl}/wp-json/${path}`, timeoutMs), { cause: error });
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -38,7 +39,11 @@ export function isNotFoundError(error) {
|
|
|
38
39
|
const cause = error?.cause;
|
|
39
40
|
return cause?.response?.statusCode === 404;
|
|
40
41
|
}
|
|
41
|
-
export function
|
|
42
|
+
export function isTimeoutError(error) {
|
|
43
|
+
const cause = error?.cause;
|
|
44
|
+
return cause?.name === 'TimeoutError';
|
|
45
|
+
}
|
|
46
|
+
export function formatWpError(error, url, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
42
47
|
const err = error;
|
|
43
48
|
const status = err.response?.statusCode;
|
|
44
49
|
if (status === 401 || status === 403) {
|
|
@@ -52,7 +57,7 @@ export function formatWpError(error, url) {
|
|
|
52
57
|
return reason ? `Request failed (${status}) on ${url}: ${reason}` : `Request failed (${status}) on ${url}.`;
|
|
53
58
|
}
|
|
54
59
|
if (err.name === 'TimeoutError') {
|
|
55
|
-
return `Request timed out after ${
|
|
60
|
+
return `Request timed out after ${timeoutMs / 1000}s on ${url}. Is the site reachable?`;
|
|
56
61
|
}
|
|
57
62
|
return `Request to ${url} failed: ${err.message ?? String(error)}`;
|
|
58
63
|
}
|
|
@@ -60,13 +65,22 @@ export function formatWpError(error, url) {
|
|
|
60
65
|
// core response (e.g. an uncaught fatal formatted by WordPress itself) uses `{"message": "..."}`.
|
|
61
66
|
// Surfacing this is what makes a deliberately clear server-side error (e.g. "Multiple snippet
|
|
62
67
|
// plugins are active...") actually reach the user instead of a bare, unhelpful status code.
|
|
68
|
+
//
|
|
69
|
+
// Some controllers (ComposerController::sync(), notably) pair a short, generic `error` (e.g.
|
|
70
|
+
// "Sync failed.") with the real detail in a separate `output` field (the raw Composer trace),
|
|
71
|
+
// specifically so a caller that only reads `error` doesn't see it — which is exactly what this
|
|
72
|
+
// function used to do, hiding the one piece of text that actually explains the failure.
|
|
63
73
|
function extractServerErrorMessage(body) {
|
|
64
74
|
if (!body)
|
|
65
75
|
return undefined;
|
|
66
76
|
try {
|
|
67
77
|
const parsed = JSON.parse(body);
|
|
68
78
|
const reason = parsed.error ?? parsed.message;
|
|
69
|
-
|
|
79
|
+
const summary = typeof reason === 'string' && reason.trim() ? reason : undefined;
|
|
80
|
+
const detail = typeof parsed.output === 'string' && parsed.output.trim() ? parsed.output.trim() : undefined;
|
|
81
|
+
if (summary && detail)
|
|
82
|
+
return `${summary}\n${detail}`;
|
|
83
|
+
return summary ?? detail;
|
|
70
84
|
}
|
|
71
85
|
catch {
|
|
72
86
|
return undefined;
|
package/dist/utils/composer.d.ts
CHANGED
package/dist/utils/seo-format.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toSlug } from './to-slug.js';
|
|
1
2
|
// Post types synced by `lps seo pull` when --post-type isn't given.
|
|
2
3
|
export const DEFAULT_POST_TYPES = ['post', 'page'];
|
|
3
4
|
export const SEO_SETTINGS_ENDPOINT = 'loopress/v1/seo/settings';
|
|
@@ -8,3 +9,7 @@ export function seoRedirectEndpoint(id) {
|
|
|
8
9
|
export function seoPostMetaEndpoint(postType) {
|
|
9
10
|
return `loopress/v1/seo/post-meta/${postType}`;
|
|
10
11
|
}
|
|
12
|
+
// The `<id>-<slug>.json` on-disk convention shared by `seo pull` and `seo push`.
|
|
13
|
+
export function redirectFileBase(redirect) {
|
|
14
|
+
return `${redirect.id}-${toSlug(redirect.urlTo, 'redirect')}`;
|
|
15
|
+
}
|
|
@@ -20,4 +20,6 @@ export declare function parseLocation(raw: unknown): null | SnippetLocation;
|
|
|
20
20
|
export declare function parseInsertMethod(raw: unknown): null | SnippetInsertMethod;
|
|
21
21
|
export declare function defaultLocationForType(type: SnippetType): SnippetLocation;
|
|
22
22
|
export declare function stripPhpOpeningTag(code: string): string;
|
|
23
|
+
export declare function buildSnippetFile(snippet: NormalizedSnippet): string;
|
|
24
|
+
export declare function buildMetaFile(snippet: NormalizedSnippet): string;
|
|
23
25
|
export declare function normalizeSnippet(data: Record<string, unknown>): NormalizedSnippet;
|
|
@@ -34,10 +34,36 @@ function resolvePriority(raw) {
|
|
|
34
34
|
}
|
|
35
35
|
// Snippet files on disk keep the <?php opening tag so they're valid, syntax-highlighted PHP files.
|
|
36
36
|
// The WordPress plugin stores just the executable body, so it's stripped before push and restored
|
|
37
|
-
// on pull (see buildSnippetFile
|
|
37
|
+
// on pull (see buildSnippetFile below).
|
|
38
38
|
export function stripPhpOpeningTag(code) {
|
|
39
39
|
return code.replace(/^<\?php\s*/i, '');
|
|
40
40
|
}
|
|
41
|
+
export function buildSnippetFile(snippet) {
|
|
42
|
+
if (snippet.type === 'php' && !snippet.code.trimStart().startsWith('<?')) {
|
|
43
|
+
return `<?php\n\n${snippet.code}`;
|
|
44
|
+
}
|
|
45
|
+
return snippet.code;
|
|
46
|
+
}
|
|
47
|
+
export function buildMetaFile(snippet) {
|
|
48
|
+
const meta = {
|
|
49
|
+
id: snippet.id,
|
|
50
|
+
name: snippet.name,
|
|
51
|
+
type: snippet.type,
|
|
52
|
+
active: snippet.active,
|
|
53
|
+
location: snippet.location,
|
|
54
|
+
};
|
|
55
|
+
if (snippet.description)
|
|
56
|
+
meta.description = snippet.description;
|
|
57
|
+
if (snippet.tags.length > 0)
|
|
58
|
+
meta.tags = snippet.tags;
|
|
59
|
+
if (snippet.insertMethod === 'shortcode')
|
|
60
|
+
meta.insertMethod = snippet.insertMethod;
|
|
61
|
+
if (snippet.priority !== 10)
|
|
62
|
+
meta.priority = snippet.priority;
|
|
63
|
+
if (snippet.shortcodeAttributes.length > 0)
|
|
64
|
+
meta.shortcodeAttributes = snippet.shortcodeAttributes;
|
|
65
|
+
return JSON.stringify(meta, null, 2) + '\n';
|
|
66
|
+
}
|
|
41
67
|
// Defensive coercion for whatever JSON the `loopress/v1/snippets` endpoint returns, in case a
|
|
42
68
|
// field is missing or malformed server-side.
|
|
43
69
|
export function normalizeSnippet(data) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toSlug(value: string, fallback?: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import slugify from 'slugify';
|
|
2
|
+
// The one slugification used everywhere a name becomes part of a filename or an api slug.
|
|
3
|
+
// `fallback` covers values that slugify to nothing (empty, punctuation-only); callers that
|
|
4
|
+
// can genuinely live with an empty slug just omit it.
|
|
5
|
+
export function toSlug(value, fallback = '') {
|
|
6
|
+
return slugify(value, { lower: true, strict: true }) || fallback;
|
|
7
|
+
}
|