@loopress/cli 0.20.1 → 0.21.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 +128 -62
- package/dist/commands/acf/list.js +2 -1
- package/dist/commands/acf/push.js +1 -1
- package/dist/commands/api/list.d.ts +7 -4
- package/dist/commands/api/list.js +3 -10
- package/dist/commands/api/publish.js +1 -1
- package/dist/commands/api/pull.d.ts +8 -1
- package/dist/commands/api/pull.js +13 -4
- package/dist/commands/api/push.d.ts +7 -1
- package/dist/commands/api/push.js +48 -18
- package/dist/commands/composer/init.js +6 -6
- package/dist/commands/composer/pull.d.ts +7 -1
- package/dist/commands/composer/pull.js +4 -2
- package/dist/commands/composer/push.d.ts +8 -1
- package/dist/commands/composer/push.js +3 -1
- package/dist/commands/dev.d.ts +13 -0
- package/dist/commands/dev.js +97 -0
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/init.js +7 -7
- package/dist/commands/login.js +3 -3
- package/dist/commands/page/list.d.ts +2 -4
- package/dist/commands/page/list.js +3 -10
- package/dist/commands/page/pull.d.ts +13 -1
- package/dist/commands/page/pull.js +5 -2
- package/dist/commands/page/push.d.ts +11 -1
- package/dist/commands/page/push.js +21 -14
- package/dist/commands/plugin/add.js +3 -3
- package/dist/commands/plugin/pull.d.ts +7 -1
- package/dist/commands/plugin/pull.js +3 -1
- package/dist/commands/plugin/push.d.ts +9 -1
- package/dist/commands/plugin/push.js +13 -9
- package/dist/commands/project/config.js +8 -8
- package/dist/commands/project/list.js +2 -2
- package/dist/commands/project/push.js +5 -5
- package/dist/commands/project/switch.js +2 -2
- package/dist/commands/snippet/list.d.ts +3 -4
- package/dist/commands/snippet/list.js +3 -10
- package/dist/commands/snippet/pull.d.ts +13 -1
- package/dist/commands/snippet/pull.js +5 -2
- package/dist/commands/snippet/push.d.ts +11 -1
- package/dist/commands/snippet/push.js +15 -5
- package/dist/commands/status.d.ts +13 -1
- package/dist/commands/status.js +25 -18
- package/dist/config/auth.manager.d.ts +2 -2
- package/dist/config/project-config.manager.d.ts +1 -1
- package/dist/config/project-config.manager.js +5 -6
- package/dist/lib/base.d.ts +2 -2
- package/dist/lib/base.js +3 -2
- package/dist/lib/bootstrap-full-install.d.ts +1 -1
- package/dist/lib/dev-watch.d.ts +16 -0
- package/dist/lib/dev-watch.js +71 -0
- package/dist/lib/find-orphaned-files.d.ts +3 -2
- package/dist/lib/find-orphaned-files.js +40 -4
- package/dist/lib/github-release.js +1 -1
- package/dist/lib/interactive.js +1 -1
- package/dist/lib/load-files.d.ts +1 -0
- package/dist/lib/load-files.js +40 -11
- package/dist/lib/load-snippets.d.ts +1 -1
- package/dist/lib/load-snippets.js +11 -9
- package/dist/lib/local-callback-server.js +8 -6
- package/dist/lib/plugin-detection.d.ts +1 -1
- package/dist/lib/push-command.js +2 -2
- package/dist/lib/rotate-app-password.d.ts +1 -1
- package/dist/lib/rotate-app-password.js +1 -1
- package/dist/lib/temp-admin.d.ts +3 -3
- package/dist/lib/temp-admin.js +2 -2
- package/dist/lib/wp-authorize-flow.js +5 -1
- package/dist/lib/wp-client.d.ts +2 -2
- package/dist/lib/wp-client.js +7 -0
- package/dist/types/plugin.d.ts +4 -4
- package/dist/types/snippet.d.ts +3 -3
- package/dist/utils/composer.d.ts +2 -2
- package/dist/utils/form-format.js +1 -1
- package/dist/utils/loopress-config.d.ts +1 -1
- package/dist/utils/page-format.js +2 -2
- package/dist/utils/plugins.d.ts +5 -5
- package/dist/utils/plugins.js +3 -3
- package/dist/utils/seo-format.d.ts +6 -6
- package/dist/utils/snippet-format.d.ts +2 -2
- package/dist/utils/snippet-format.js +10 -5
- package/oclif.manifest.json +153 -31
- package/package.json +9 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
1
2
|
import { createServer } from 'node:http';
|
|
2
3
|
import { openBrowser } from './open-browser.js';
|
|
3
4
|
async function readBody(req) {
|
|
@@ -13,7 +14,7 @@ function parseFormData(body) {
|
|
|
13
14
|
* Sends the user to a URL in their browser and catches the resulting redirect on a short-lived
|
|
14
15
|
* local server; this factors out the server setup, timeout, and browser-opening boilerplate.
|
|
15
16
|
*/
|
|
16
|
-
export function waitForLocalCallback(options) {
|
|
17
|
+
export async function waitForLocalCallback(options) {
|
|
17
18
|
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
|
|
18
19
|
return new Promise((resolve, reject) => {
|
|
19
20
|
function finish(res, page, settle) {
|
|
@@ -23,13 +24,13 @@ export function waitForLocalCallback(options) {
|
|
|
23
24
|
server.close();
|
|
24
25
|
settle();
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
async function handleIncoming(req, res) {
|
|
27
28
|
try {
|
|
28
29
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
29
30
|
const body = req.method === 'POST' ? parseFormData(await readBody(req)) : {};
|
|
30
31
|
options.handleRequest(url, {
|
|
31
|
-
rejectWithPage
|
|
32
|
-
resolveWithPage
|
|
32
|
+
rejectWithPage(page, error) { finish(res, page, () => { reject(error); }); },
|
|
33
|
+
resolveWithPage(page, value) { finish(res, page, () => { resolve(value); }); },
|
|
33
34
|
respondBadRequest(message) {
|
|
34
35
|
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
35
36
|
res.end(message);
|
|
@@ -41,9 +42,10 @@ export function waitForLocalCallback(options) {
|
|
|
41
42
|
res.writeHead(500);
|
|
42
43
|
res.end('Internal error');
|
|
43
44
|
server.close();
|
|
44
|
-
reject(error);
|
|
45
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|
|
48
|
+
const server = createServer((req, res) => { void handleIncoming(req, res); });
|
|
47
49
|
server.on('error', (err) => {
|
|
48
50
|
clearTimeout(timer);
|
|
49
51
|
reject(err);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WpClient } from './wp-client.js';
|
|
1
|
+
import { type WpClient } from './wp-client.js';
|
|
2
2
|
/**
|
|
3
3
|
* Checks `wp/v2/plugins` directly rather than through `parseInstalledPlugins`
|
|
4
4
|
* (`src/utils/plugins.ts`), which deliberately filters Loopress's own plugin slugs out of its
|
package/dist/lib/push-command.js
CHANGED
|
@@ -24,8 +24,8 @@ export class PushCommand extends LoopressCommand {
|
|
|
24
24
|
this.refusedByGuard = true;
|
|
25
25
|
this.error('Target environment is "production". Pass --yes to confirm the push in a non-interactive run.');
|
|
26
26
|
}
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
27
|
+
const isProceed = await confirm({ default: true, message: `Push to production (${this.siteConfig.url})?` });
|
|
28
|
+
if (!isProceed) {
|
|
29
29
|
this.refusedByGuard = true;
|
|
30
30
|
this.error('Aborted.');
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EnvironmentConfig } from '../types/config.js';
|
|
1
|
+
import { type EnvironmentConfig } from '../types/config.js';
|
|
2
2
|
export declare const APP_PASSWORD_MAX_AGE_MS: number;
|
|
3
3
|
export declare function isAppPasswordStale(addedAt: string): boolean;
|
|
4
4
|
export declare function rotateAppPassword(env: EnvironmentConfig & {
|
|
@@ -7,7 +7,7 @@ export function isAppPasswordStale(addedAt) {
|
|
|
7
7
|
// Create the new credential, confirm it actually authenticates on its own, only then revoke
|
|
8
8
|
// the old one. Reversing this order risks locking the user out if the new one is somehow bad.
|
|
9
9
|
export async function rotateAppPassword(env) {
|
|
10
|
-
const [user] = env.token.split(':');
|
|
10
|
+
const [user] = env.token.split(':', 1);
|
|
11
11
|
const oldClient = new WpClient(env.url, env.token);
|
|
12
12
|
const old = await oldClient.get(`${APPLICATION_PASSWORDS_PATH}/introspect`);
|
|
13
13
|
const created = await oldClient.post(APPLICATION_PASSWORDS_PATH, { name: 'Loopress' });
|
package/dist/lib/temp-admin.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { WpClient } from './wp-client.js';
|
|
2
|
-
export
|
|
1
|
+
import { type WpClient } from './wp-client.js';
|
|
2
|
+
export type TempAdmin = {
|
|
3
3
|
id: number;
|
|
4
4
|
password: string;
|
|
5
5
|
username: string;
|
|
6
|
-
}
|
|
6
|
+
};
|
|
7
7
|
/**
|
|
8
8
|
* Creates a temporary WordPress administrator, the only way to obtain `install_plugins` when
|
|
9
9
|
* all the CLI has is an application password. `.invalid` is the RFC 2606 reserved TLD for
|
package/dist/lib/temp-admin.js
CHANGED
|
@@ -32,7 +32,7 @@ export async function deleteTempAdmin(wp, admin) {
|
|
|
32
32
|
catch (error) {
|
|
33
33
|
throw new Error(`Failed to remove the temporary admin account "${admin.username}" (id ${admin.id}) from the site. Remove it manually from wp-admin.`, { cause: error });
|
|
34
34
|
}
|
|
35
|
-
const
|
|
35
|
+
const isStillExists = await wp
|
|
36
36
|
.get(`wp/v2/users/${admin.id}`)
|
|
37
37
|
.then(() => true)
|
|
38
38
|
.catch((error) => {
|
|
@@ -40,7 +40,7 @@ export async function deleteTempAdmin(wp, admin) {
|
|
|
40
40
|
return false;
|
|
41
41
|
throw error;
|
|
42
42
|
});
|
|
43
|
-
if (
|
|
43
|
+
if (isStillExists) {
|
|
44
44
|
throw new Error(`Temporary admin account "${admin.username}" (id ${admin.id}) still exists after deletion. Remove it manually from wp-admin.`);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -16,7 +16,7 @@ import { renderResultPage, waitForLocalCallback } from './local-callback-server.
|
|
|
16
16
|
* Application Password to the local callback server.
|
|
17
17
|
* 6. The local server extracts the credentials and resolves the promise.
|
|
18
18
|
*/
|
|
19
|
-
export function authorizeWithBrowser(siteUrl, log) {
|
|
19
|
+
export async function authorizeWithBrowser(siteUrl, log) {
|
|
20
20
|
return waitForLocalCallback({
|
|
21
21
|
buildUrl(callbackBaseUrl) {
|
|
22
22
|
const relayUrl = 'https://api.loopress.dev/auth/wp-authorize';
|
|
@@ -31,7 +31,11 @@ export function authorizeWithBrowser(siteUrl, log) {
|
|
|
31
31
|
rejectWithPage(REJECTED_PAGE, new Error('Authorization rejected in WordPress.'));
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
// `||`, not `??`: an empty-string field counts as absent here, same as the `!password`
|
|
35
|
+
// check below, so falls through to the query-param fallback instead of stopping at "".
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
34
37
|
const password = body.password || url.searchParams.get('password') || '';
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
35
39
|
const userLogin = body.user_login || url.searchParams.get('user_login') || '';
|
|
36
40
|
if (!password || !userLogin) {
|
|
37
41
|
respondBadRequest('Missing password or user_login');
|
package/dist/lib/wp-client.d.ts
CHANGED
|
@@ -13,9 +13,9 @@ export declare class WpClient {
|
|
|
13
13
|
put<T = unknown>(path: string, json?: Record<string, unknown>, options?: RequestOptions): Promise<T>;
|
|
14
14
|
private request;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
type RequestOptions = {
|
|
17
17
|
timeoutMs?: number;
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
export declare function isNotFoundError(error: unknown): boolean;
|
|
20
20
|
export declare function isTimeoutError(error: unknown): boolean;
|
|
21
21
|
export declare function formatWpError(error: unknown, url: string, timeoutMs?: number): string;
|
package/dist/lib/wp-client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import got from 'got';
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
2
3
|
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
3
4
|
/**
|
|
4
5
|
* HTTP client for a WordPress site's REST API.
|
|
@@ -81,6 +82,12 @@ function extractServerErrorMessage(body) {
|
|
|
81
82
|
return undefined;
|
|
82
83
|
try {
|
|
83
84
|
const parsed = JSON.parse(body);
|
|
85
|
+
// WP core's own generic body for a route it never registered (e.g. a plugin too old to
|
|
86
|
+
// expose this endpoint yet) is never more useful than the "is the plugin up to date?"
|
|
87
|
+
// fallback below; surfacing it verbatim buries a version-skew mismatch behind text that
|
|
88
|
+
// reads like the endpoint doesn't exist at all, pointing the user in the wrong direction.
|
|
89
|
+
if (parsed.code === 'rest_no_route')
|
|
90
|
+
return undefined;
|
|
84
91
|
const reason = parsed.error ?? parsed.message;
|
|
85
92
|
const summary = typeof reason === 'string' && reason.trim() ? reason : undefined;
|
|
86
93
|
const detail = typeof parsed.output === 'string' && parsed.output.trim() ? parsed.output.trim() : undefined;
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type InstalledPlugin = {
|
|
2
2
|
active: boolean;
|
|
3
3
|
file: string;
|
|
4
4
|
name: string;
|
|
5
5
|
slug: string;
|
|
6
6
|
version: string;
|
|
7
|
-
}
|
|
8
|
-
export
|
|
7
|
+
};
|
|
8
|
+
export type WpNativePlugin = {
|
|
9
9
|
name: string;
|
|
10
10
|
plugin: string;
|
|
11
11
|
status: 'active' | 'inactive' | 'network-active';
|
|
12
12
|
version: string;
|
|
13
|
-
}
|
|
13
|
+
};
|
|
14
14
|
export type PluginManifest = Record<string, string>;
|
package/dist/types/snippet.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SnippetInsertMethod, SnippetLocation, SnippetType } from '../utils/snippet-format.js';
|
|
2
|
-
export
|
|
1
|
+
import { type SnippetInsertMethod, type SnippetLocation, type SnippetType } from '../utils/snippet-format.js';
|
|
2
|
+
export type Snippet = {
|
|
3
3
|
active: boolean;
|
|
4
4
|
code: string;
|
|
5
5
|
id?: number;
|
|
@@ -11,4 +11,4 @@ export interface Snippet {
|
|
|
11
11
|
shortcodeAttributes: string[];
|
|
12
12
|
tags: string[];
|
|
13
13
|
type: SnippetType;
|
|
14
|
-
}
|
|
14
|
+
};
|
package/dist/utils/composer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type ComposerJson = {
|
|
2
2
|
config?: {
|
|
3
3
|
'allow-plugins'?: Record<string, boolean>;
|
|
4
4
|
};
|
|
@@ -12,7 +12,7 @@ export interface ComposerJson {
|
|
|
12
12
|
}>;
|
|
13
13
|
require?: Record<string, string>;
|
|
14
14
|
'require-dev'?: Record<string, string>;
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
export declare function readComposerJson(): Promise<ComposerJson | null>;
|
|
17
17
|
export declare function readComposerLock(): Promise<null | string>;
|
|
18
18
|
export declare function getComposerManagedSlugs(composerJson: ComposerJson): string[];
|
|
@@ -8,7 +8,7 @@ export const FORM_ENDPOINT = 'loopress/v1/forms';
|
|
|
8
8
|
// through pull/push untouched.
|
|
9
9
|
export function getFormId(data) {
|
|
10
10
|
const id = Number(data.id);
|
|
11
|
-
return Number.
|
|
11
|
+
return Number.isSafeInteger(id) && id > 0 ? id : null;
|
|
12
12
|
}
|
|
13
13
|
export function getFormTitle(data) {
|
|
14
14
|
const settings = data.settings;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LoopressProjectConfiguration } from '../types/project-config.generated.js';
|
|
1
|
+
import { type LoopressProjectConfiguration } from '../types/project-config.generated.js';
|
|
2
2
|
export type LoopressLocalConfig = LoopressProjectConfiguration;
|
|
3
3
|
export declare function readLocalConfig(): Promise<LoopressLocalConfig>;
|
|
4
4
|
export declare function writeLocalConfig(config: LoopressLocalConfig): Promise<void>;
|
|
@@ -7,7 +7,7 @@ export const PAGE_ENDPOINT = 'wp/v2/pages';
|
|
|
7
7
|
export const PAGE_LIST_QUERY = 'per_page=100';
|
|
8
8
|
export function getPageId(data) {
|
|
9
9
|
const id = Number(data.id);
|
|
10
|
-
return Number.
|
|
10
|
+
return Number.isSafeInteger(id) && id > 0 ? id : null;
|
|
11
11
|
}
|
|
12
12
|
// `title`/`content`/`excerpt` come back as `{raw, rendered}` from `?context=edit` (what `page
|
|
13
13
|
// pull` requests) but round-trip fine as plain strings too for a hand-created file, same
|
|
@@ -56,7 +56,7 @@ const PAGE_META_FIELDS = [
|
|
|
56
56
|
export function pickPageMeta(data) {
|
|
57
57
|
const meta = {};
|
|
58
58
|
for (const field of PAGE_META_FIELDS) {
|
|
59
|
-
if (field
|
|
59
|
+
if (Object.hasOwn(data, field))
|
|
60
60
|
meta[field] = data[field];
|
|
61
61
|
}
|
|
62
62
|
return meta;
|
package/dist/utils/plugins.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { InstalledPlugin, PluginManifest, WpNativePlugin } from '../types/plugin.js';
|
|
2
|
-
export
|
|
1
|
+
import { type InstalledPlugin, type PluginManifest, type WpNativePlugin } from '../types/plugin.js';
|
|
2
|
+
export type PluginDiff = {
|
|
3
3
|
toActivate: Array<{
|
|
4
4
|
file: string;
|
|
5
5
|
slug: string;
|
|
@@ -8,8 +8,8 @@ export interface PluginDiff {
|
|
|
8
8
|
slug: string;
|
|
9
9
|
}>;
|
|
10
10
|
upToDate: string[];
|
|
11
|
-
}
|
|
12
|
-
export
|
|
11
|
+
};
|
|
12
|
+
export type MergeResult = {
|
|
13
13
|
added: string[];
|
|
14
14
|
merged: PluginManifest;
|
|
15
15
|
updated: Array<{
|
|
@@ -17,7 +17,7 @@ export interface MergeResult {
|
|
|
17
17
|
slug: string;
|
|
18
18
|
to: string;
|
|
19
19
|
}>;
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
export declare function mergePluginManifest(existing: PluginManifest, incoming: PluginManifest): MergeResult;
|
|
22
22
|
export declare function parseInstalledPlugins(raw: WpNativePlugin[]): InstalledPlugin[];
|
|
23
23
|
export declare function diffPlugins(manifest: PluginManifest, installed: InstalledPlugin[]): PluginDiff;
|
package/dist/utils/plugins.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
const LOOPRESS_PLUGIN_SLUGS = new Set(['loopress', 'loopress-full', 'loopress-light']);
|
|
7
7
|
export function mergePluginManifest(existing, incoming) {
|
|
8
8
|
const merged = { ...existing, ...incoming };
|
|
9
|
-
const added = Object.keys(incoming).filter((s) => !(s
|
|
9
|
+
const added = Object.keys(incoming).filter((s) => !Object.hasOwn(existing, s));
|
|
10
10
|
const updated = Object.keys(incoming)
|
|
11
|
-
.filter((s) => s
|
|
11
|
+
.filter((s) => Object.hasOwn(existing, s) && existing[s] !== incoming[s])
|
|
12
12
|
.map((s) => ({ from: existing[s], slug: s, to: incoming[s] }));
|
|
13
13
|
return { added, merged, updated };
|
|
14
14
|
}
|
|
@@ -16,7 +16,7 @@ export function mergePluginManifest(existing, incoming) {
|
|
|
16
16
|
// single-file plugin) with the `.php` extension stripped; the WordPress.org slug is just the
|
|
17
17
|
// folder name (or the bare id itself for a single-file plugin).
|
|
18
18
|
function slugFromPluginFile(file) {
|
|
19
|
-
return file.split('/')[0];
|
|
19
|
+
return file.split('/', 1)[0];
|
|
20
20
|
}
|
|
21
21
|
export function parseInstalledPlugins(raw) {
|
|
22
22
|
return raw
|
|
@@ -3,19 +3,19 @@ export declare const SEO_SETTINGS_ENDPOINT = "loopress/v1/seo/settings";
|
|
|
3
3
|
export declare const SEO_REDIRECTS_ENDPOINT = "loopress/v1/seo/redirects";
|
|
4
4
|
export declare function seoRedirectEndpoint(id: number): string;
|
|
5
5
|
export declare function seoPostMetaEndpoint(postType: string): string;
|
|
6
|
-
export
|
|
6
|
+
export type SeoPostMeta = {
|
|
7
7
|
meta: Record<string, unknown>;
|
|
8
8
|
slug: string;
|
|
9
9
|
title: string;
|
|
10
|
-
}
|
|
11
|
-
export
|
|
12
|
-
createdAt:
|
|
10
|
+
};
|
|
11
|
+
export type SeoRedirect = {
|
|
12
|
+
createdAt: string | undefined;
|
|
13
13
|
headerCode: number;
|
|
14
14
|
hits: number;
|
|
15
15
|
id: number;
|
|
16
16
|
sources: unknown;
|
|
17
17
|
status: string;
|
|
18
|
-
updatedAt:
|
|
18
|
+
updatedAt: string | undefined;
|
|
19
19
|
urlTo: string;
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
export declare function redirectFileBase(redirect: SeoRedirect): string;
|
|
@@ -2,7 +2,7 @@ export declare const SNIPPETS_ENDPOINT = "loopress/v1/snippets";
|
|
|
2
2
|
export type SnippetType = 'css' | 'html' | 'js' | 'php' | 'text';
|
|
3
3
|
export type SnippetInsertMethod = 'auto' | 'shortcode';
|
|
4
4
|
export type SnippetLocation = 'admin' | 'body' | 'everywhere' | 'footer' | 'frontend' | 'header' | 'once';
|
|
5
|
-
export
|
|
5
|
+
export type NormalizedSnippet = {
|
|
6
6
|
active: boolean;
|
|
7
7
|
code: string;
|
|
8
8
|
description: string;
|
|
@@ -14,7 +14,7 @@ export interface NormalizedSnippet {
|
|
|
14
14
|
shortcodeAttributes: string[];
|
|
15
15
|
tags: string[];
|
|
16
16
|
type: SnippetType;
|
|
17
|
-
}
|
|
17
|
+
};
|
|
18
18
|
export declare function parseType(raw: unknown): null | SnippetType;
|
|
19
19
|
export declare function parseLocation(raw: unknown): null | SnippetLocation;
|
|
20
20
|
export declare function parseInsertMethod(raw: unknown): null | SnippetInsertMethod;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
export const SNIPPETS_ENDPOINT = 'loopress/v1/snippets';
|
|
2
|
+
// Coerces server JSON that should be a string but, being unknown, could in principle be an
|
|
3
|
+
// object; a plain String() on that would print "[object Object]" instead of falling back.
|
|
4
|
+
function coerceString(value, fallback = '') {
|
|
5
|
+
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? String(value) : fallback;
|
|
6
|
+
}
|
|
2
7
|
export function parseType(raw) {
|
|
3
8
|
const valid = ['css', 'html', 'js', 'php', 'text'];
|
|
4
|
-
const value =
|
|
9
|
+
const value = coerceString(raw).toLowerCase();
|
|
5
10
|
return valid.includes(value) ? value : null;
|
|
6
11
|
}
|
|
7
12
|
const VALID_LOCATIONS = new Set(['admin', 'body', 'everywhere', 'footer', 'frontend', 'header', 'once']);
|
|
8
13
|
export function parseLocation(raw) {
|
|
9
|
-
const value =
|
|
14
|
+
const value = coerceString(raw).toLowerCase();
|
|
10
15
|
return VALID_LOCATIONS.has(value) ? value : null;
|
|
11
16
|
}
|
|
12
17
|
export function parseInsertMethod(raw) {
|
|
@@ -69,12 +74,12 @@ export function buildMetaFile(snippet) {
|
|
|
69
74
|
export function normalizeSnippet(data) {
|
|
70
75
|
return {
|
|
71
76
|
active: Boolean(data.active),
|
|
72
|
-
code:
|
|
73
|
-
description:
|
|
77
|
+
code: coerceString(data.code),
|
|
78
|
+
description: coerceString(data.description),
|
|
74
79
|
id: Number(data.id),
|
|
75
80
|
insertMethod: parseInsertMethod(data.insertMethod) ?? 'auto',
|
|
76
81
|
location: parseLocation(data.location) ?? defaultLocationForType(parseType(data.type) ?? 'php'),
|
|
77
|
-
name:
|
|
82
|
+
name: coerceString(data.name),
|
|
78
83
|
priority: resolvePriority(data.priority),
|
|
79
84
|
shortcodeAttributes: Array.isArray(data.shortcodeAttributes) ? data.shortcodeAttributes.map(String) : [],
|
|
80
85
|
tags: Array.isArray(data.tags) ? data.tags : [],
|