@loopress/cli 0.23.0 → 0.24.1
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 +603 -94
- package/dist/commands/acf/diff.d.ts +2 -0
- package/dist/commands/acf/diff.js +5 -0
- package/dist/commands/api/diff.d.ts +2 -0
- package/dist/commands/api/diff.js +5 -0
- package/dist/commands/api/list.d.ts +2 -12
- package/dist/commands/api/list.js +2 -20
- package/dist/commands/api/pull.d.ts +2 -20
- package/dist/commands/api/pull.js +2 -53
- package/dist/commands/api/push.d.ts +2 -22
- package/dist/commands/api/push.js +2 -116
- package/dist/commands/api/rm.d.ts +2 -0
- package/dist/commands/api/rm.js +2 -0
- package/dist/commands/composer/pull.js +6 -16
- package/dist/commands/composer/push.d.ts +3 -0
- package/dist/commands/composer/push.js +24 -7
- package/dist/commands/dev.js +1 -1
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +42 -0
- package/dist/commands/form/diff.d.ts +2 -0
- package/dist/commands/form/diff.js +5 -0
- package/dist/commands/form/push.d.ts +2 -0
- package/dist/commands/form/push.js +11 -4
- package/dist/commands/hook/diff.d.ts +2 -0
- package/dist/commands/hook/diff.js +5 -0
- package/dist/commands/hook/list.d.ts +2 -0
- package/dist/commands/hook/list.js +2 -0
- package/dist/commands/hook/pull.d.ts +2 -0
- package/dist/commands/hook/pull.js +2 -0
- package/dist/commands/hook/push.d.ts +2 -0
- package/dist/commands/hook/push.js +2 -0
- package/dist/commands/hook/rm.d.ts +2 -0
- package/dist/commands/hook/rm.js +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/login.js +3 -2
- package/dist/commands/option/add.d.ts +15 -0
- package/dist/commands/option/add.js +47 -0
- package/dist/commands/option/diff.d.ts +2 -0
- package/dist/commands/option/diff.js +5 -0
- package/dist/commands/{page → option}/list.d.ts +5 -1
- package/dist/commands/option/list.js +151 -0
- package/dist/commands/{page → option}/pull.d.ts +3 -12
- package/dist/commands/option/pull.js +75 -0
- package/dist/commands/{page → option}/push.d.ts +3 -8
- package/dist/commands/option/push.js +66 -0
- package/dist/commands/option/remove.d.ts +22 -0
- package/dist/commands/option/remove.js +69 -0
- package/dist/commands/plugin/push.d.ts +0 -1
- package/dist/commands/plugin/push.js +3 -10
- package/dist/commands/pull.js +3 -2
- package/dist/commands/push.js +4 -3
- package/dist/commands/seo/diff.d.ts +2 -0
- package/dist/commands/seo/diff.js +5 -0
- package/dist/commands/seo/push.d.ts +2 -0
- package/dist/commands/seo/push.js +16 -4
- package/dist/commands/snippet/diff.d.ts +2 -0
- package/dist/commands/snippet/diff.js +5 -0
- package/dist/commands/theme/push.d.ts +0 -1
- package/dist/commands/theme/push.js +3 -10
- package/dist/hooks/finally.js +4 -1
- package/dist/lib/base.d.ts +2 -1
- package/dist/lib/base.js +12 -22
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +1 -2
- package/dist/lib/diff-command.d.ts +47 -0
- package/dist/lib/diff-command.js +180 -0
- package/dist/lib/diff-state.d.ts +22 -0
- package/dist/lib/diff-state.js +88 -0
- package/dist/lib/find-orphaned-files.js +1 -1
- package/dist/lib/interactive.d.ts +1 -0
- package/dist/lib/interactive.js +10 -0
- package/dist/lib/local-callback-server.d.ts +14 -1
- package/dist/lib/local-callback-server.js +40 -1
- package/dist/lib/php-files-command.d.ts +67 -0
- package/dist/lib/php-files-command.js +338 -0
- package/dist/lib/put-or-create.js +1 -1
- package/dist/lib/readdir-tolerant.d.ts +1 -0
- package/dist/lib/readdir-tolerant.js +3 -1
- package/dist/lib/resource-state.d.ts +14 -0
- package/dist/lib/resource-state.js +361 -0
- package/dist/lib/sentry.d.ts +3 -0
- package/dist/lib/sentry.js +30 -0
- package/dist/lib/validate-local.js +15 -7
- package/dist/lib/wp-authorize-flow.d.ts +4 -0
- package/dist/lib/wp-authorize-flow.js +10 -8
- package/dist/lib/wp-client.d.ts +5 -0
- package/dist/lib/wp-client.js +56 -0
- package/dist/types/project-config.generated.d.ts +8 -4
- package/dist/utils/option-format.d.ts +31 -0
- package/dist/utils/option-format.js +69 -0
- package/dist/utils/plugin-sync.d.ts +6 -0
- package/dist/utils/resource-dirs.d.ts +13 -0
- package/dist/utils/resource-dirs.js +34 -0
- package/oclif.manifest.json +1488 -301
- package/package.json +11 -8
- package/schemas/project-config.schema.json +10 -5
- package/dist/commands/page/list.js +0 -19
- package/dist/commands/page/pull.js +0 -57
- package/dist/commands/page/push.js +0 -132
- package/dist/utils/page-format.d.ts +0 -7
- package/dist/utils/page-format.js +0 -78
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopress/cli",
|
|
3
3
|
"description": "CLI tool for syncing WordPress code snippets, plugins, and Composer dependencies via the REST API",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.24.1",
|
|
5
5
|
"author": "jean-smaug",
|
|
6
6
|
"bin": {
|
|
7
7
|
"loopress": "bin/run.js",
|
|
@@ -11,19 +11,22 @@
|
|
|
11
11
|
"url": "https://github.com/loopress/loopress/issues"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@inquirer/prompts": "^8.7.
|
|
14
|
+
"@inquirer/prompts": "^8.7.2",
|
|
15
15
|
"@oclif/core": "^5.0.0",
|
|
16
16
|
"@oclif/plugin-help": "^7.0.0",
|
|
17
17
|
"@oclif/plugin-warn-if-update-available": "^4.0.0",
|
|
18
|
-
"@sentry/node": "^10.
|
|
18
|
+
"@sentry/node": "^10.74.0",
|
|
19
19
|
"chokidar": "^5.0.0",
|
|
20
20
|
"compare-versions": "^6.1.1",
|
|
21
|
+
"diff": "^9.0.0",
|
|
21
22
|
"glob": "13.0.6",
|
|
22
23
|
"got": "^16.0.0",
|
|
23
24
|
"listr2": "^11.1.0",
|
|
25
|
+
"microdiff": "^1.6.0",
|
|
24
26
|
"open": "^11.0.2",
|
|
25
|
-
"playwright-core": "^1.
|
|
27
|
+
"playwright-core": "^1.63.0",
|
|
26
28
|
"slugify": "^1.6.9",
|
|
29
|
+
"tty-table": "^5.0.0",
|
|
27
30
|
"write-file-atomic": "^8.0.0"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
@@ -31,10 +34,10 @@
|
|
|
31
34
|
"@oclif/prettier-config": "^0.2.1",
|
|
32
35
|
"@stryker-mutator/core": "^10.0.0",
|
|
33
36
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
34
|
-
"@types/node": "26.
|
|
37
|
+
"@types/node": "26.5.0",
|
|
35
38
|
"@types/write-file-atomic": "^4.0.3",
|
|
36
|
-
"@vitest/coverage-v8": "
|
|
37
|
-
"eslint": "^10.
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
40
|
+
"eslint": "^10.10.0",
|
|
38
41
|
"eslint-config-oclif": "^7.1.7",
|
|
39
42
|
"eslint-config-prettier": "^10",
|
|
40
43
|
"eslint-plugin-sonarjs": "^4.2.0",
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"ts-node": "10.9.2",
|
|
46
49
|
"tsx": "4.23.13",
|
|
47
50
|
"typescript": "^6.0.3",
|
|
48
|
-
"vitest": "^
|
|
51
|
+
"vitest": "^4.1.11"
|
|
49
52
|
},
|
|
50
53
|
"engines": {
|
|
51
54
|
"node": ">=18.0.0"
|
|
@@ -34,21 +34,26 @@
|
|
|
34
34
|
"description": "Directory where forms (WPForms today) are read from and written to, relative to rootDir.",
|
|
35
35
|
"default": "forms"
|
|
36
36
|
},
|
|
37
|
-
"pageDir": {
|
|
38
|
-
"type": "string",
|
|
39
|
-
"description": "Directory where WordPress pages are read from and written to, relative to rootDir.",
|
|
40
|
-
"default": "pages"
|
|
41
|
-
},
|
|
42
37
|
"apiDir": {
|
|
43
38
|
"type": "string",
|
|
44
39
|
"description": "Directory where custom API route files are read from and written to, relative to rootDir.",
|
|
45
40
|
"default": "api"
|
|
46
41
|
},
|
|
42
|
+
"hooksDir": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Directory where hook files (WordPress actions, filters, and scheduled tasks) are read from and written to, relative to rootDir.",
|
|
45
|
+
"default": "hooks"
|
|
46
|
+
},
|
|
47
47
|
"appsDir": {
|
|
48
48
|
"type": "string",
|
|
49
49
|
"description": "Directory where single-page app bundles are read from and written to, relative to rootDir. Each subdirectory is one app: a loopress.app.json plus a built dist/ folder.",
|
|
50
50
|
"default": "apps"
|
|
51
51
|
},
|
|
52
|
+
"optionsDir": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Directory where explicitly tracked WordPress options (wp_options rows) are read from and written to, relative to rootDir. Only options added via `lps option add` live here, this is never the whole options table.",
|
|
55
|
+
"default": "options"
|
|
56
|
+
},
|
|
52
57
|
"projectId": {
|
|
53
58
|
"type": "string",
|
|
54
59
|
"description": "Project identifier from the global Loopress config. When set, takes precedence over the currently active project in the global config."
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
-
import { getPageId, getPageTitle, PAGE_ENDPOINT, PAGE_LIST_QUERY } from '../../utils/page-format.js';
|
|
3
|
-
export default class List extends LoopressCommand {
|
|
4
|
-
static description = 'List pages from WordPress';
|
|
5
|
-
static enableJsonFlag = true;
|
|
6
|
-
static examples = ['$ lps page list'];
|
|
7
|
-
async run() {
|
|
8
|
-
const pages = await this.wp.get(`${PAGE_ENDPOINT}?${PAGE_LIST_QUERY}`);
|
|
9
|
-
this.log(`Pages (${pages.length}):`);
|
|
10
|
-
if (pages.length === 0) {
|
|
11
|
-
this.log(' (none)');
|
|
12
|
-
return pages;
|
|
13
|
-
}
|
|
14
|
-
for (const page of pages) {
|
|
15
|
-
this.log(` ${getPageId(page) ?? '(no id)'}. ${getPageTitle(page)}`);
|
|
16
|
-
}
|
|
17
|
-
return pages;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Args } from '@oclif/core';
|
|
2
|
-
import { writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
5
|
-
import { findOrphanedFiles, numericPrefixKey } from '../../lib/find-orphaned-files.js';
|
|
6
|
-
import { getPageContent, getPageId, getPageTitle, PAGE_ENDPOINT, PAGE_LIST_QUERY, pageFileBase, pickPageMeta } from '../../utils/page-format.js';
|
|
7
|
-
import { pluralize } from '../../utils/pluralize.js';
|
|
8
|
-
export default class Pull extends LoopressCommand {
|
|
9
|
-
static args = {
|
|
10
|
-
path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
|
|
11
|
-
};
|
|
12
|
-
static description = 'Pull pages from WordPress';
|
|
13
|
-
static enableJsonFlag = true;
|
|
14
|
-
static examples = ['$ lps page pull'];
|
|
15
|
-
static flags = {
|
|
16
|
-
...LoopressCommand.dryRunFlag,
|
|
17
|
-
...LoopressCommand.yesFlag,
|
|
18
|
-
};
|
|
19
|
-
async run() {
|
|
20
|
-
const { args } = await this.parse(Pull);
|
|
21
|
-
const { url } = this.siteConfig;
|
|
22
|
-
const path = this.resolvePagePath(args.path);
|
|
23
|
-
this.log(`Pulling pages from ${url}`);
|
|
24
|
-
this.log(`Pages path: ${path}`);
|
|
25
|
-
// context=edit returns title/content/excerpt as raw editable source instead of rendered
|
|
26
|
-
// HTML, needed for the file to be a faithful, re-pushable copy (requires edit_pages).
|
|
27
|
-
const remoteList = await this.wp.get(`${PAGE_ENDPOINT}?${PAGE_LIST_QUERY}&context=edit`);
|
|
28
|
-
const withId = remoteList.filter((page) => getPageId(page) !== null);
|
|
29
|
-
const skipped = remoteList.length - withId.length;
|
|
30
|
-
// Every page writes an `.html` (content) and `.json` (everything else) pair; both
|
|
31
|
-
// extensions are candidates for orphan cleanup, same principle as `snippet pull`.
|
|
32
|
-
const orphans = await findOrphanedFiles(path, new Set(withId.map((page) => String(getPageId(page)))), {
|
|
33
|
-
extensions: ['.json', '.html'],
|
|
34
|
-
key: numericPrefixKey,
|
|
35
|
-
});
|
|
36
|
-
const pulled = withId.map((page) => ({ id: getPageId(page), title: getPageTitle(page) }));
|
|
37
|
-
await this.pullDirectory(path, withId, orphans, {
|
|
38
|
-
dryRunMessage: `Would pull ${pluralize(withId.length, 'page')} to ${path}`,
|
|
39
|
-
orphanReason: `in ${path} no longer present on WordPress`,
|
|
40
|
-
pulledMessage: `Pulled ${pluralize(withId.length, 'page')} to ${path}`,
|
|
41
|
-
title: (page) => getPageTitle(page),
|
|
42
|
-
async write(page, writeDir) {
|
|
43
|
-
const base = pageFileBase(getPageId(page), getPageTitle(page));
|
|
44
|
-
// `content` lives entirely in the `.html` file; the `.json` sidecar keeps only the
|
|
45
|
-
// fields WordPress actually accepts back on write, see pickPageMeta.
|
|
46
|
-
await writeFile(join(writeDir, `${base}.html`), getPageContent(page));
|
|
47
|
-
await writeFile(join(writeDir, `${base}.json`), JSON.stringify(pickPageMeta(page), null, 2) + '\n');
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
if (this.dryRun)
|
|
51
|
-
return { orphans, pulled, skipped, status: 'dry-run' };
|
|
52
|
-
if (skipped > 0) {
|
|
53
|
-
this.warn(`${pluralize(skipped, 'page')} skipped because they have no id`);
|
|
54
|
-
}
|
|
55
|
-
return { orphans, pulled, skipped, status: 'success' };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { Args } from '@oclif/core';
|
|
2
|
-
import { readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { basename, dirname, extname, join } from 'node:path';
|
|
4
|
-
import { PushCommand } from '../../lib/push-command.js';
|
|
5
|
-
import { putOrCreate } from '../../lib/put-or-create.js';
|
|
6
|
-
import { readdirTolerant } from '../../lib/readdir-tolerant.js';
|
|
7
|
-
import { getPageId, getPageTitle, PAGE_ENDPOINT, pageFileBase } from '../../utils/page-format.js';
|
|
8
|
-
import { pluralize } from '../../utils/pluralize.js';
|
|
9
|
-
export default class Push extends PushCommand {
|
|
10
|
-
static args = {
|
|
11
|
-
path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
|
|
12
|
-
};
|
|
13
|
-
static description = 'Push pages to WordPress. Local files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.';
|
|
14
|
-
static enableJsonFlag = true;
|
|
15
|
-
static examples = ['$ lps page push'];
|
|
16
|
-
static flags = {
|
|
17
|
-
...PushCommand.dryRunFlag,
|
|
18
|
-
...PushCommand.yesFlag,
|
|
19
|
-
};
|
|
20
|
-
async run() {
|
|
21
|
-
const { args } = await this.parse(Push);
|
|
22
|
-
const { url } = this.siteConfig;
|
|
23
|
-
const path = this.resolvePagePath(args.path);
|
|
24
|
-
this.log(`Pushing pages to ${url}`);
|
|
25
|
-
this.log(`Pages path: ${path}`);
|
|
26
|
-
const pages = await this.loadFiles(path);
|
|
27
|
-
this.log(`Found ${pluralize(pages.length, 'page')} to push`);
|
|
28
|
-
const pushed = [];
|
|
29
|
-
await this.runPushTasks(pages, (page) => getPageTitle(page.meta), async (page, task) => {
|
|
30
|
-
const id = await this.pushPage(page, task);
|
|
31
|
-
pushed.push({ id, title: getPageTitle(page.meta) });
|
|
32
|
-
});
|
|
33
|
-
if (this.failedCount > 0) {
|
|
34
|
-
this.error(`${pluralize(this.failedCount, 'page')} failed to push.`);
|
|
35
|
-
}
|
|
36
|
-
if (this.dryRun)
|
|
37
|
-
return { pushed, status: 'dry-run' };
|
|
38
|
-
await this.recordSuccess();
|
|
39
|
-
this.log('All pages pushed.');
|
|
40
|
-
return { pushed, status: 'success' };
|
|
41
|
-
}
|
|
42
|
-
// Renames the local file pair to the `<id>-<slug>` convention used by `page pull` whenever it
|
|
43
|
-
// doesn't already match, same principle as ensureCanonicalFilename in commands/snippet/push.ts:
|
|
44
|
-
// the id is persisted into the sidecar before any rename, so a crash mid-rename still leaves a
|
|
45
|
-
// valid, matchable pair on disk instead of an orphaned one that would be recreated as a duplicate.
|
|
46
|
-
async ensureCanonicalFilename(page, id, title) {
|
|
47
|
-
const dir = dirname(page.contentPath);
|
|
48
|
-
const canonicalBase = pageFileBase(id, title);
|
|
49
|
-
const currentBase = basename(page.contentPath, '.html');
|
|
50
|
-
const meta = { ...page.meta, id };
|
|
51
|
-
await writeFile(page.metaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
52
|
-
if (currentBase === canonicalBase)
|
|
53
|
-
return;
|
|
54
|
-
await rename(page.contentPath, join(dir, `${canonicalBase}.html`));
|
|
55
|
-
await rename(page.metaPath, join(dir, `${canonicalBase}.json`));
|
|
56
|
-
}
|
|
57
|
-
// A page is its `.html` content file plus an optional `.json` sidecar for everything else
|
|
58
|
-
// (a fresh hand-created page may have no sidecar yet). One page is read in isolation: a
|
|
59
|
-
// corrupted or hand-broken sidecar must only skip that page, not abort loading the rest of
|
|
60
|
-
// the directory, same principle as commands/form/push.ts.
|
|
61
|
-
async loadFiles(dir) {
|
|
62
|
-
const files = await readdirTolerant(dir);
|
|
63
|
-
const pages = [];
|
|
64
|
-
for (const file of files) {
|
|
65
|
-
if (extname(file) !== '.html')
|
|
66
|
-
continue;
|
|
67
|
-
const contentPath = join(dir, file);
|
|
68
|
-
const metaPath = join(dir, `${basename(file, '.html')}.json`);
|
|
69
|
-
let meta = {};
|
|
70
|
-
try {
|
|
71
|
-
const parsed = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
72
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
73
|
-
this.warn(`Skipping "${metaPath}": not a JSON object`);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
meta = parsed;
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (error.code !== 'ENOENT') {
|
|
80
|
-
this.warn(`Skipping "${metaPath}": ${error.message}`);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
let content;
|
|
85
|
-
try {
|
|
86
|
-
content = await readFile(contentPath, 'utf8');
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
this.warn(`Skipping "${contentPath}": ${error.message}`);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
pages.push({ content, contentPath, meta, metaPath });
|
|
93
|
-
}
|
|
94
|
-
return pages;
|
|
95
|
-
}
|
|
96
|
-
// ponytail: `parent` round-trips as the source site's numeric page id untouched, so pushing
|
|
97
|
-
// to a site where that id belongs to a different page (or nothing) silently mis-parents or
|
|
98
|
-
// orphans the page. No id-remapping across sites here; fine for same-hierarchy environments,
|
|
99
|
-
// revisit if pushing across sites with divergent page trees becomes a real workflow.
|
|
100
|
-
async pushPage(page, task) {
|
|
101
|
-
const title = getPageTitle(page.meta);
|
|
102
|
-
if (this.dryRun) {
|
|
103
|
-
if (task)
|
|
104
|
-
task.output = `[dry-run] Would push: ${title}`;
|
|
105
|
-
return getPageId(page.meta);
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
const id = getPageId(page.meta);
|
|
109
|
-
const payload = { ...page.meta, content: page.content };
|
|
110
|
-
// WordPress core rejects any POST that still carries a (now stale) `id` field with a
|
|
111
|
-
// 400 "Cannot create existing post", regardless of whether that id actually exists.
|
|
112
|
-
const postPayload = { ...payload };
|
|
113
|
-
delete postPayload.id;
|
|
114
|
-
const { body, created } = await putOrCreate(this.wp, {
|
|
115
|
-
id,
|
|
116
|
-
payload,
|
|
117
|
-
postEndpoint: PAGE_ENDPOINT,
|
|
118
|
-
postPayload,
|
|
119
|
-
putEndpoint: (pageId) => `${PAGE_ENDPOINT}/${pageId}`,
|
|
120
|
-
});
|
|
121
|
-
const resultId = created ? getPageId(body) : id;
|
|
122
|
-
if (resultId !== null)
|
|
123
|
-
await this.ensureCanonicalFilename(page, resultId, title);
|
|
124
|
-
if (task)
|
|
125
|
-
task.output = `Pushed: ${title}`;
|
|
126
|
-
return resultId;
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
this.reportTaskFailure(`Failed to push ${title}: ${error.message}`, error, task);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const PAGE_ENDPOINT = "wp/v2/pages";
|
|
2
|
-
export declare const PAGE_LIST_QUERY = "per_page=100";
|
|
3
|
-
export declare function getPageId(data: Record<string, unknown>): null | number;
|
|
4
|
-
export declare function getPageTitle(data: Record<string, unknown>): string;
|
|
5
|
-
export declare function pageFileBase(id: number, title: string): string;
|
|
6
|
-
export declare function pickPageMeta(data: Record<string, unknown>): Record<string, unknown>;
|
|
7
|
-
export declare function getPageContent(data: Record<string, unknown>): string;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { toSlug } from './to-slug.js';
|
|
2
|
-
// WordPress core's own REST endpoint (`wp/v2/pages`), unlike acf/form/seo/snippet there is no
|
|
3
|
-
// Loopress-plugin controller behind this one, same principle as `wp/v2/plugins` in plugin push/pull.
|
|
4
|
-
export const PAGE_ENDPOINT = 'wp/v2/pages';
|
|
5
|
-
// ponytail: fixed cap, not a paged loop; fine for typical page counts, revisit with real
|
|
6
|
-
// pagination (X-WP-TotalPages) if a site ever has more than 100 pages.
|
|
7
|
-
export const PAGE_LIST_QUERY = 'per_page=100';
|
|
8
|
-
export function getPageId(data) {
|
|
9
|
-
const id = Number(data.id);
|
|
10
|
-
return Number.isSafeInteger(id) && id > 0 ? id : null;
|
|
11
|
-
}
|
|
12
|
-
// `title`/`content`/`excerpt` come back as `{raw, rendered}` from `?context=edit` (what `page
|
|
13
|
-
// pull` requests) but round-trip fine as plain strings too for a hand-created file, same
|
|
14
|
-
// looseness as getFormTitle in form-format.ts.
|
|
15
|
-
export function getPageTitle(data) {
|
|
16
|
-
const { title } = data;
|
|
17
|
-
if (typeof title === 'string' && title.trim() !== '')
|
|
18
|
-
return title;
|
|
19
|
-
if (title && typeof title === 'object') {
|
|
20
|
-
const { raw, rendered } = title;
|
|
21
|
-
if (typeof raw === 'string' && raw.trim() !== '')
|
|
22
|
-
return raw;
|
|
23
|
-
if (typeof rendered === 'string' && rendered.trim() !== '')
|
|
24
|
-
return rendered;
|
|
25
|
-
}
|
|
26
|
-
return '(untitled)';
|
|
27
|
-
}
|
|
28
|
-
export function pageFileBase(id, title) {
|
|
29
|
-
return `${id}-${toSlug(title, 'untitled')}`;
|
|
30
|
-
}
|
|
31
|
-
// The fields WordPress core actually accepts on write to `wp/v2/pages` (confirmed via
|
|
32
|
-
// `OPTIONS wp/v2/pages`), plus `id` for local identity. A GET response carries a lot more:
|
|
33
|
-
// `guid`, `link`, `modified`/`modified_gmt`, `permalink_template`, `generated_slug`,
|
|
34
|
-
// `class_list`, `type`, `_links`, all of it readonly/computed by WordPress, most of it
|
|
35
|
-
// domain- or revision-specific. `_links` alone is the bulk of the file and, like
|
|
36
|
-
// `modified`/`modified_gmt`, changes on every edit regardless of whether the page's actual
|
|
37
|
-
// content changed, which would make every `page pull` produce a spurious git diff if kept.
|
|
38
|
-
const PAGE_META_FIELDS = [
|
|
39
|
-
'id',
|
|
40
|
-
'date',
|
|
41
|
-
'date_gmt',
|
|
42
|
-
'slug',
|
|
43
|
-
'status',
|
|
44
|
-
'password',
|
|
45
|
-
'parent',
|
|
46
|
-
'title',
|
|
47
|
-
'author',
|
|
48
|
-
'excerpt',
|
|
49
|
-
'featured_media',
|
|
50
|
-
'comment_status',
|
|
51
|
-
'ping_status',
|
|
52
|
-
'menu_order',
|
|
53
|
-
'meta',
|
|
54
|
-
'template',
|
|
55
|
-
];
|
|
56
|
-
export function pickPageMeta(data) {
|
|
57
|
-
const meta = {};
|
|
58
|
-
for (const field of PAGE_META_FIELDS) {
|
|
59
|
-
if (Object.hasOwn(data, field))
|
|
60
|
-
meta[field] = data[field];
|
|
61
|
-
}
|
|
62
|
-
return meta;
|
|
63
|
-
}
|
|
64
|
-
// `content` comes back as `{raw, rendered}` from `?context=edit` (what `page pull` requests);
|
|
65
|
-
// a hand-created file only has the plain string. This is the one field split out into its own
|
|
66
|
-
// `.html` file by pull/push: raw Gutenberg block markup escaped inside a JSON string is neither
|
|
67
|
-
// readable nor safely hand-editable, same reasoning as the code/meta split in snippet-format.ts.
|
|
68
|
-
export function getPageContent(data) {
|
|
69
|
-
const { content } = data;
|
|
70
|
-
if (typeof content === 'string')
|
|
71
|
-
return content;
|
|
72
|
-
if (content && typeof content === 'object') {
|
|
73
|
-
const { raw } = content;
|
|
74
|
-
if (typeof raw === 'string')
|
|
75
|
-
return raw;
|
|
76
|
-
}
|
|
77
|
-
return '';
|
|
78
|
-
}
|