@loopress/cli 0.29.0 → 0.31.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 +1369 -360
- package/dist/commands/acf/push.js +2 -1
- package/dist/commands/composer/push.d.ts +1 -0
- package/dist/commands/composer/push.js +13 -0
- package/dist/commands/content/copy.d.ts +28 -0
- package/dist/commands/content/copy.js +97 -0
- package/dist/commands/cpt/diff.js +5 -0
- package/dist/commands/cpt/list.d.ts +2 -0
- package/dist/commands/cpt/list.js +4 -0
- package/dist/commands/cpt/pull.d.ts +2 -0
- package/dist/commands/cpt/pull.js +4 -0
- package/dist/commands/cpt/push.d.ts +2 -0
- package/dist/commands/cpt/push.js +4 -0
- package/dist/commands/cpt/rm.d.ts +2 -0
- package/dist/commands/cpt/rm.js +3 -0
- package/dist/commands/cpt/rollback.js +5 -0
- package/dist/commands/dev.js +14 -4
- package/dist/commands/diff.js +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +57 -2
- package/dist/commands/form/push.js +4 -1
- package/dist/commands/meta/diff.d.ts +2 -0
- package/dist/commands/meta/diff.js +5 -0
- package/dist/commands/meta/list.d.ts +19 -0
- package/dist/commands/meta/list.js +21 -0
- package/dist/commands/meta/pull.d.ts +2 -0
- package/dist/commands/meta/pull.js +4 -0
- package/dist/commands/meta/push.d.ts +2 -0
- package/dist/commands/meta/push.js +4 -0
- package/dist/commands/meta/rm.d.ts +2 -0
- package/dist/commands/meta/rm.js +3 -0
- package/dist/commands/meta/rollback.d.ts +2 -0
- package/dist/commands/meta/rollback.js +5 -0
- package/dist/commands/option/list.js +2 -8
- package/dist/commands/option/push.js +4 -2
- package/dist/commands/page/push.js +3 -2
- package/dist/commands/pattern/diff.d.ts +2 -0
- package/dist/commands/pattern/diff.js +5 -0
- package/dist/commands/pattern/list.d.ts +2 -0
- package/dist/commands/pattern/list.js +3 -0
- package/dist/commands/pattern/pull.d.ts +2 -0
- package/dist/commands/pattern/pull.js +4 -0
- package/dist/commands/pattern/push.d.ts +2 -0
- package/dist/commands/pattern/push.js +4 -0
- package/dist/commands/pattern/rm.d.ts +2 -0
- package/dist/commands/pattern/rm.js +3 -0
- package/dist/commands/pattern/rollback.d.ts +2 -0
- package/dist/commands/pattern/rollback.js +5 -0
- package/dist/commands/plugin/audit.js +43 -25
- package/dist/commands/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +20 -1
- package/dist/commands/project/list.d.ts +1 -1
- package/dist/commands/project/list.js +6 -6
- package/dist/commands/project/pull.js +11 -8
- package/dist/commands/project/push.js +23 -18
- package/dist/commands/project/remove.js +1 -1
- package/dist/commands/project/switch.d.ts +0 -1
- package/dist/commands/project/switch.js +16 -36
- package/dist/commands/promote.d.ts +1 -0
- package/dist/commands/promote.js +7 -3
- package/dist/commands/pull.d.ts +2 -1
- package/dist/commands/pull.js +31 -6
- package/dist/commands/push.d.ts +4 -2
- package/dist/commands/push.js +51 -19
- package/dist/commands/role/diff.d.ts +2 -0
- package/dist/commands/role/diff.js +5 -0
- package/dist/commands/role/list.d.ts +2 -0
- package/dist/commands/role/list.js +4 -0
- package/dist/commands/role/pull.d.ts +2 -0
- package/dist/commands/role/pull.js +4 -0
- package/dist/commands/role/push.d.ts +2 -0
- package/dist/commands/role/push.js +4 -0
- package/dist/commands/role/rm.d.ts +2 -0
- package/dist/commands/role/rm.js +3 -0
- package/dist/commands/role/rollback.d.ts +2 -0
- package/dist/commands/role/rollback.js +5 -0
- package/dist/commands/snippet/push.d.ts +1 -0
- package/dist/commands/snippet/push.js +24 -3
- package/dist/commands/status.d.ts +2 -3
- package/dist/commands/status.js +21 -53
- package/dist/commands/taxonomy/diff.d.ts +2 -0
- package/dist/commands/taxonomy/diff.js +5 -0
- package/dist/commands/taxonomy/list.d.ts +2 -0
- package/dist/commands/taxonomy/list.js +5 -0
- package/dist/commands/taxonomy/pull.d.ts +2 -0
- package/dist/commands/taxonomy/pull.js +4 -0
- package/dist/commands/taxonomy/push.d.ts +2 -0
- package/dist/commands/taxonomy/push.js +4 -0
- package/dist/commands/taxonomy/rm.d.ts +2 -0
- package/dist/commands/taxonomy/rm.js +3 -0
- package/dist/commands/taxonomy/rollback.d.ts +2 -0
- package/dist/commands/taxonomy/rollback.js +5 -0
- package/dist/commands/theme/template/push.d.ts +2 -0
- package/dist/commands/theme/template/push.js +26 -5
- package/dist/commands/theme/version/pull.js +6 -1
- package/dist/config/project-config.manager.d.ts +3 -3
- package/dist/config/project-config.manager.js +22 -31
- package/dist/hooks/finally.js +11 -3
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/app-build.js +3 -1
- package/dist/lib/base.d.ts +2 -2
- package/dist/lib/base.js +33 -38
- package/dist/lib/declared-type-command.d.ts +35 -0
- package/dist/lib/declared-type-command.js +192 -0
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +7 -3
- package/dist/lib/diff-command.d.ts +2 -0
- package/dist/lib/diff-command.js +24 -3
- package/dist/lib/github-release.js +4 -0
- package/dist/lib/load-snippets.js +4 -0
- package/dist/lib/local-callback-server.js +3 -1
- package/dist/lib/php-files-command.d.ts +8 -0
- package/dist/lib/php-files-command.js +5 -0
- package/dist/lib/push-command.d.ts +5 -2
- package/dist/lib/push-command.js +96 -15
- package/dist/lib/put-or-create.js +3 -4
- package/dist/lib/resource-state.js +38 -85
- package/dist/lib/rollback-command.js +9 -2
- package/dist/lib/rollback-materialize.js +13 -24
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +72 -0
- package/dist/lib/temp-admin.d.ts +10 -1
- package/dist/lib/temp-admin.js +27 -2
- package/dist/lib/validate-local.js +40 -12
- package/dist/lib/wp-client.d.ts +1 -0
- package/dist/lib/wp-client.js +21 -2
- package/dist/lib/wp-site-diagnostic.js +3 -2
- package/dist/types/global-config.generated.d.ts +1 -5
- package/dist/types/project-config.generated.d.ts +28 -4
- package/dist/types/snippet.d.ts +1 -0
- package/dist/utils/composer.js +2 -1
- package/dist/utils/declared-type-format.d.ts +52 -0
- package/dist/utils/declared-type-format.js +233 -0
- package/dist/utils/resource-dirs.d.ts +9 -3
- package/dist/utils/resource-dirs.js +16 -4
- package/dist/utils/template-format.d.ts +1 -0
- package/dist/utils/theme-css.d.ts +19 -0
- package/dist/utils/theme-css.js +100 -0
- package/oclif.manifest.json +3294 -1213
- package/package.json +32 -16
- package/schemas/global-config.schema.json +2 -6
- package/schemas/project-config.schema.json +37 -7
- package/dist/commands/seo/diff.js +0 -5
- package/dist/commands/seo/list.d.ts +0 -18
- package/dist/commands/seo/list.js +0 -58
- package/dist/commands/seo/pull.d.ts +0 -24
- package/dist/commands/seo/pull.js +0 -89
- package/dist/commands/seo/push.d.ts +0 -29
- package/dist/commands/seo/push.js +0 -178
- package/dist/commands/seo/rollback.js +0 -5
- package/dist/utils/seo-format.d.ts +0 -29
- package/dist/utils/seo-format.js +0 -18
- /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
- /package/dist/commands/{seo → cpt}/rollback.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopress/cli",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "Build, review and ship WordPress, in one loop: pull, diff, push and roll back a site's code and config from git",
|
|
4
|
+
"version": "0.31.0",
|
|
5
5
|
"author": "jean-smaug",
|
|
6
6
|
"bin": {
|
|
7
7
|
"loopress": "bin/run.js",
|
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
"url": "https://github.com/loopress/loopress/issues"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@inquirer/prompts": "^8.7.
|
|
15
|
-
"@oclif/core": "^5.1.
|
|
16
|
-
"@oclif/plugin-help": "^7.0.
|
|
17
|
-
"@oclif/plugin-warn-if-update-available": "^4.0.
|
|
18
|
-
"@sentry/node": "^
|
|
14
|
+
"@inquirer/prompts": "^8.7.3",
|
|
15
|
+
"@oclif/core": "^5.1.7",
|
|
16
|
+
"@oclif/plugin-help": "^7.0.4",
|
|
17
|
+
"@oclif/plugin-warn-if-update-available": "^4.0.3",
|
|
18
|
+
"@sentry/node": "^11.6.0",
|
|
19
19
|
"chokidar": "^5.0.0",
|
|
20
20
|
"compare-versions": "^6.1.1",
|
|
21
21
|
"diff": "^9.0.0",
|
|
22
|
+
"esbuild": "0.28.2",
|
|
22
23
|
"got": "^16.0.0",
|
|
23
24
|
"listr2": "^11.1.1",
|
|
24
25
|
"microdiff": "^1.6.0",
|
|
25
26
|
"open": "^11.0.4",
|
|
26
|
-
"playwright-core": "^1.
|
|
27
|
+
"playwright-core": "^1.64.0",
|
|
27
28
|
"slugify": "^1.6.9",
|
|
28
29
|
"tty-table": "^5.0.0",
|
|
29
30
|
"write-file-atomic": "^8.0.0"
|
|
@@ -32,22 +33,22 @@
|
|
|
32
33
|
"@oclif/prettier-config": "^0.2.1",
|
|
33
34
|
"@stryker-mutator/core": "^10.0.0",
|
|
34
35
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
35
|
-
"@types/node": "26.6.
|
|
36
|
+
"@types/node": "26.6.5",
|
|
36
37
|
"@types/write-file-atomic": "^4.0.3",
|
|
37
38
|
"@vitest/coverage-v8": "^4.1.11",
|
|
38
39
|
"@vitest/eslint-plugin": "^1.6.27",
|
|
39
40
|
"eslint": "^10.12.0",
|
|
40
|
-
"eslint-config-oclif": "^7.1.
|
|
41
|
+
"eslint-config-oclif": "^7.1.10",
|
|
41
42
|
"eslint-config-prettier": "^10",
|
|
42
43
|
"eslint-plugin-sonarjs": "^4.2.2",
|
|
43
44
|
"json-schema-to-typescript": "^16.0.0",
|
|
44
|
-
"oclif": "^6.0.
|
|
45
|
-
"prettier": "3.9.
|
|
45
|
+
"oclif": "^6.0.6",
|
|
46
|
+
"prettier": "3.9.10",
|
|
46
47
|
"shx": "^0.4.0",
|
|
47
48
|
"ts-node": "10.9.2",
|
|
48
49
|
"tsx": "4.23.15",
|
|
49
50
|
"typescript": "^6.0.3",
|
|
50
|
-
"vitest": "^
|
|
51
|
+
"vitest": "^4.1.11"
|
|
51
52
|
},
|
|
52
53
|
"engines": {
|
|
53
54
|
"node": ">=22.0.0"
|
|
@@ -103,6 +104,12 @@
|
|
|
103
104
|
"api": {
|
|
104
105
|
"description": "Manage custom WordPress REST API routes"
|
|
105
106
|
},
|
|
107
|
+
"content": {
|
|
108
|
+
"description": "Copy an environment's content (posts, pages, terms) into the local site, never the reverse"
|
|
109
|
+
},
|
|
110
|
+
"cpt": {
|
|
111
|
+
"description": "Manage custom post types declared in cpt/<slug>.json files, and list every post type on the site"
|
|
112
|
+
},
|
|
106
113
|
"composer": {
|
|
107
114
|
"description": "Sync composer.json and composer.lock between your project and WordPress"
|
|
108
115
|
},
|
|
@@ -112,9 +119,6 @@
|
|
|
112
119
|
"project": {
|
|
113
120
|
"description": "Manage WordPress project and environment configurations"
|
|
114
121
|
},
|
|
115
|
-
"seo": {
|
|
116
|
-
"description": "Manage SEO settings, post meta, and redirects (RankMath or Yoast SEO, whichever is active)"
|
|
117
|
-
},
|
|
118
122
|
"snippet": {
|
|
119
123
|
"description": "Manage WordPress code snippets"
|
|
120
124
|
},
|
|
@@ -130,6 +134,18 @@
|
|
|
130
134
|
"theme:style": {
|
|
131
135
|
"description": "Manage the active block theme's Global Styles customizations (Site Editor > Styles: colors, typography, spacing)"
|
|
132
136
|
},
|
|
137
|
+
"pattern": {
|
|
138
|
+
"description": "Manage block patterns declared in patterns/<slug>.html files (registered as loopress/<slug>), and list the theme and plugin ones"
|
|
139
|
+
},
|
|
140
|
+
"role": {
|
|
141
|
+
"description": "Manage user roles declared in roles/<slug>.json files (never which users have them), and list every role on the site"
|
|
142
|
+
},
|
|
143
|
+
"meta": {
|
|
144
|
+
"description": "Manage post meta keys declared in meta/<post-type>.json files (never their values), and list every registered meta key"
|
|
145
|
+
},
|
|
146
|
+
"taxonomy": {
|
|
147
|
+
"description": "Manage taxonomies declared in taxonomies/<slug>.json files (never their terms), and list every taxonomy on the site"
|
|
148
|
+
},
|
|
133
149
|
"telemetry": {
|
|
134
150
|
"description": "Manage error reporting preferences"
|
|
135
151
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"required": ["currentProject", "projects"],
|
|
9
9
|
"properties": {
|
|
10
10
|
"currentProject": {
|
|
11
|
-
"description": "Pointer to the currently active project
|
|
11
|
+
"description": "Pointer to the currently active project, or null if none is selected. The environment is not stored: commands use --env, else the project's \"local\" environment, else its only one.",
|
|
12
12
|
"oneOf": [{"type": "null"}, {"$ref": "#/definitions/CurrentProjectPointer"}]
|
|
13
13
|
},
|
|
14
14
|
"projects": {
|
|
@@ -26,12 +26,8 @@
|
|
|
26
26
|
"title": "CurrentProjectPointer",
|
|
27
27
|
"type": "object",
|
|
28
28
|
"additionalProperties": false,
|
|
29
|
-
"required": ["
|
|
29
|
+
"required": ["id"],
|
|
30
30
|
"properties": {
|
|
31
|
-
"env": {
|
|
32
|
-
"type": "string",
|
|
33
|
-
"description": "Name of the currently active environment."
|
|
34
|
-
},
|
|
35
31
|
"id": {
|
|
36
32
|
"type": "string",
|
|
37
33
|
"description": "Id of the currently active project."
|
|
@@ -24,11 +24,6 @@
|
|
|
24
24
|
"description": "Directory where ACF field groups, post types, taxonomies and options pages are read from and written to, relative to rootDir.",
|
|
25
25
|
"default": "acf"
|
|
26
26
|
},
|
|
27
|
-
"seoDir": {
|
|
28
|
-
"type": "string",
|
|
29
|
-
"description": "Directory where SEO settings, post meta, and redirects (RankMath and Yoast SEO, whichever is active) are read from and written to, relative to rootDir.",
|
|
30
|
-
"default": "seo"
|
|
31
|
-
},
|
|
32
27
|
"formDir": {
|
|
33
28
|
"type": "string",
|
|
34
29
|
"description": "Directory where forms (WPForms today) are read from and written to, relative to rootDir.",
|
|
@@ -57,18 +52,53 @@
|
|
|
57
52
|
"templateDir": {
|
|
58
53
|
"type": "string",
|
|
59
54
|
"description": "Directory where block templates (one <slug>.html per template, written to the Loopress child theme of the active block theme) are read from, relative to rootDir. A static page uses one with its `template` header.",
|
|
60
|
-
"default": "templates"
|
|
55
|
+
"default": "theme/templates"
|
|
61
56
|
},
|
|
62
57
|
"partDir": {
|
|
63
58
|
"type": "string",
|
|
64
59
|
"description": "Directory where block template parts (one <slug>.html per part, written to the Loopress child theme of the active block theme) are read from, relative to rootDir.",
|
|
65
|
-
"default": "parts"
|
|
60
|
+
"default": "theme/parts"
|
|
61
|
+
},
|
|
62
|
+
"cssDir": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "Directory of the theme's global CSS, relative to rootDir. Its style.css is the entry: it @imports the other files, bundled into the style.css of the Loopress child theme and loaded on every page.",
|
|
65
|
+
"default": "theme/css"
|
|
66
|
+
},
|
|
67
|
+
"blockCssDir": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Directory of per-block CSS (one <namespace>/<block>.css per block, e.g. core/button.css), relative to rootDir. Each file is bundled into the Loopress child theme and only loaded on pages that render that block.",
|
|
70
|
+
"default": "theme/blocks"
|
|
66
71
|
},
|
|
67
72
|
"optionsDir": {
|
|
68
73
|
"type": "string",
|
|
69
74
|
"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.",
|
|
70
75
|
"default": "options"
|
|
71
76
|
},
|
|
77
|
+
"cptDir": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Directory where custom post types (one JSON file of register_post_type() arguments per post type, named <slug>.json) are read from and written to, relative to rootDir.",
|
|
80
|
+
"default": "cpt"
|
|
81
|
+
},
|
|
82
|
+
"taxonomyDir": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Directory where taxonomies (one JSON file of register_taxonomy() arguments plus \"object_type\" per taxonomy, named <slug>.json) are read from and written to, relative to rootDir.",
|
|
85
|
+
"default": "taxonomies"
|
|
86
|
+
},
|
|
87
|
+
"roleDir": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Directory where user roles (one JSON file of \"name\" and \"capabilities\" per role, named <slug>.json) are read from and written to, relative to rootDir.",
|
|
90
|
+
"default": "roles"
|
|
91
|
+
},
|
|
92
|
+
"patternDir": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Directory where block patterns (one <slug>.html of block markup per pattern, registered as loopress/<slug>) are read from and written to, relative to rootDir.",
|
|
95
|
+
"default": "patterns"
|
|
96
|
+
},
|
|
97
|
+
"metaDir": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Directory where post meta keys (one JSON file per post type, named <post-type>.json, mapping each meta key to its register_post_meta() arguments) are read from and written to, relative to rootDir.",
|
|
100
|
+
"default": "meta"
|
|
101
|
+
},
|
|
72
102
|
"menuDir": {
|
|
73
103
|
"type": "string",
|
|
74
104
|
"description": "Directory where nav menus (one JSON file per menu, plus menu-locations.json for the active theme's menu locations) are read from and written to, relative to rootDir.",
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { resourceDiffCommand } from '../../lib/diff-command.js';
|
|
2
|
-
export default resourceDiffCommand('seo', {
|
|
3
|
-
description: 'Show what differs in SEO settings, post meta, and redirects between your local files and a WordPress environment, or between two environments',
|
|
4
|
-
pathNoun: 'SEO directory',
|
|
5
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
-
import { type SeoPostMeta, type SeoRedirect } from '../../utils/seo-format.js';
|
|
3
|
-
type ListResult = {
|
|
4
|
-
postMeta: Record<string, SeoPostMeta[]>;
|
|
5
|
-
redirects: SeoRedirect[] | undefined;
|
|
6
|
-
redirectsUnsupported: string | undefined;
|
|
7
|
-
};
|
|
8
|
-
export default class List extends LoopressCommand {
|
|
9
|
-
static description: string;
|
|
10
|
-
static enableJsonFlag: boolean;
|
|
11
|
-
static examples: string[];
|
|
12
|
-
static flags: {
|
|
13
|
-
'post-type': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
-
};
|
|
15
|
-
run(): Promise<ListResult>;
|
|
16
|
-
private fetchRedirects;
|
|
17
|
-
}
|
|
18
|
-
export {};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
2
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
3
|
-
import { DEFAULT_POST_TYPES, SEO_REDIRECTS_ENDPOINT, seoPostMetaEndpoint } from '../../utils/seo-format.js';
|
|
4
|
-
export default class List extends LoopressCommand {
|
|
5
|
-
static description = 'List posts with SEO meta, and redirects if supported by the active SEO plugin, on WordPress';
|
|
6
|
-
static enableJsonFlag = true;
|
|
7
|
-
static examples = ['$ lps seo list', '$ lps seo list --post-type post'];
|
|
8
|
-
static flags = {
|
|
9
|
-
'post-type': Flags.string({ description: 'Limit to specific post types', multiple: true }),
|
|
10
|
-
};
|
|
11
|
-
async run() {
|
|
12
|
-
const { flags } = await this.parse(List);
|
|
13
|
-
const postTypes = flags['post-type'] && flags['post-type'].length > 0 ? flags['post-type'] : [...DEFAULT_POST_TYPES];
|
|
14
|
-
const byType = {};
|
|
15
|
-
for (const postType of postTypes) {
|
|
16
|
-
byType[postType] = await this.wp.get(seoPostMetaEndpoint(postType));
|
|
17
|
-
}
|
|
18
|
-
const { redirects, unsupportedReason } = await this.fetchRedirects();
|
|
19
|
-
for (const postType of postTypes) {
|
|
20
|
-
const posts = byType[postType];
|
|
21
|
-
this.log(`${postType} (${posts.length}):`);
|
|
22
|
-
if (posts.length === 0) {
|
|
23
|
-
this.log(' (none)');
|
|
24
|
-
this.log('');
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
for (const post of posts) {
|
|
28
|
-
this.log(` ${post.slug}. ${post.title}`);
|
|
29
|
-
}
|
|
30
|
-
this.log('');
|
|
31
|
-
}
|
|
32
|
-
if (unsupportedReason) {
|
|
33
|
-
this.log(`redirects: ${unsupportedReason}`);
|
|
34
|
-
return { postMeta: byType, redirects: undefined, redirectsUnsupported: unsupportedReason };
|
|
35
|
-
}
|
|
36
|
-
this.log(`redirects (${redirects.length}):`);
|
|
37
|
-
if (redirects.length > 0) {
|
|
38
|
-
for (const redirect of redirects) {
|
|
39
|
-
this.log(` ${redirect.id}. [${redirect.status}] ${redirect.headerCode} -> ${redirect.urlTo}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
this.log(' (none)');
|
|
44
|
-
}
|
|
45
|
-
return { postMeta: byType, redirects: redirects, redirectsUnsupported: undefined };
|
|
46
|
-
}
|
|
47
|
-
// Redirects are only supported by some SeoProvider backends (RankMath, not Yoast); reported
|
|
48
|
-
// as a line in the listing rather than failing the whole command, since post meta above may
|
|
49
|
-
// well have succeeded.
|
|
50
|
-
async fetchRedirects() {
|
|
51
|
-
try {
|
|
52
|
-
return { redirects: await this.wp.get(SEO_REDIRECTS_ENDPOINT), unsupportedReason: undefined };
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
return { redirects: null, unsupportedReason: error.message };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
-
type SeoPullResult = {
|
|
3
|
-
path: string;
|
|
4
|
-
postTypes: string[];
|
|
5
|
-
status: 'dry-run' | 'success';
|
|
6
|
-
};
|
|
7
|
-
export default class Pull extends LoopressCommand {
|
|
8
|
-
static args: {
|
|
9
|
-
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
10
|
-
};
|
|
11
|
-
static description: string;
|
|
12
|
-
static enableJsonFlag: boolean;
|
|
13
|
-
static examples: string[];
|
|
14
|
-
static flags: {
|
|
15
|
-
'post-type': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
-
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
-
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
-
};
|
|
19
|
-
run(): Promise<SeoPullResult>;
|
|
20
|
-
private pullPostMeta;
|
|
21
|
-
private pullRedirects;
|
|
22
|
-
private pullSettings;
|
|
23
|
-
}
|
|
24
|
-
export {};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { LoopressCommand } from '../../lib/base.js';
|
|
5
|
-
import { basenameKey, findOrphanedFiles, numericPrefixKey } from '../../lib/find-orphaned-files.js';
|
|
6
|
-
import { DEFAULT_POST_TYPES, redirectFileBase, SEO_REDIRECTS_ENDPOINT, SEO_SETTINGS_ENDPOINT, seoPostMetaEndpoint, } from '../../utils/seo-format.js';
|
|
7
|
-
export default class Pull extends LoopressCommand {
|
|
8
|
-
static args = {
|
|
9
|
-
path: Args.string({ description: 'Path to SEO directory (overrides project config)' }),
|
|
10
|
-
};
|
|
11
|
-
static description = 'Pull SEO settings, post meta, and (if supported) redirects from WordPress';
|
|
12
|
-
static enableJsonFlag = true;
|
|
13
|
-
static examples = ['$ lps seo pull', '$ lps seo pull --post-type post --post-type page'];
|
|
14
|
-
static flags = {
|
|
15
|
-
...LoopressCommand.dryRunFlag,
|
|
16
|
-
...LoopressCommand.yesFlag,
|
|
17
|
-
'post-type': Flags.string({ description: 'Limit post meta to specific post types', multiple: true }),
|
|
18
|
-
};
|
|
19
|
-
async run() {
|
|
20
|
-
const { args, flags } = await this.parse(Pull);
|
|
21
|
-
const { url } = this.siteConfig;
|
|
22
|
-
const path = this.resolveSeoPath(args.path);
|
|
23
|
-
const postTypes = flags['post-type'] && flags['post-type'].length > 0 ? flags['post-type'] : [...DEFAULT_POST_TYPES];
|
|
24
|
-
this.log(`Pulling SEO configuration from ${url}`);
|
|
25
|
-
this.log(`SEO path: ${path}`);
|
|
26
|
-
await this.pullSettings(path);
|
|
27
|
-
for (const postType of postTypes) {
|
|
28
|
-
await this.pullPostMeta(postType, path);
|
|
29
|
-
}
|
|
30
|
-
await this.pullRedirects(path);
|
|
31
|
-
return { path, postTypes, status: this.dryRun ? 'dry-run' : 'success' };
|
|
32
|
-
}
|
|
33
|
-
async pullPostMeta(postType, basePath) {
|
|
34
|
-
const dir = join(basePath, 'post-meta', postType);
|
|
35
|
-
const remote = await this.wp.get(seoPostMetaEndpoint(postType));
|
|
36
|
-
const orphans = await findOrphanedFiles(dir, new Set(remote.map((post) => post.slug)), {
|
|
37
|
-
extensions: ['.json'],
|
|
38
|
-
key: basenameKey,
|
|
39
|
-
});
|
|
40
|
-
await this.pullDirectory(dir, remote, orphans, {
|
|
41
|
-
dryRunMessage: `Would pull ${remote.length} ${postType} post-meta file(s) to ${dir}`,
|
|
42
|
-
orphanReason: `in ${dir} no longer present on WordPress`,
|
|
43
|
-
pulledMessage: `Pulled ${remote.length} ${postType} post-meta file(s) to ${dir}`,
|
|
44
|
-
title: (post) => post.slug,
|
|
45
|
-
// `revision` (#234) is never persisted locally, same as RemoteOption.revision: it's only
|
|
46
|
-
// ever read fresh, right before a push, never trusted from a stale local file.
|
|
47
|
-
write: async ({ meta, slug, title }, writeDir) => writeFile(join(writeDir, `${slug}.json`), JSON.stringify({ meta, slug, title }, null, 2) + '\n'),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
// Redirects are only supported by some SeoProvider backends (RankMath, not Yoast). Unlike
|
|
51
|
-
// push (which must fail loudly if the user has local redirect files that can't be synced),
|
|
52
|
-
// pull degrades gracefully here: the active plugin never supporting redirects isn't an error
|
|
53
|
-
// in the same sense a deleted-on-WordPress file is, there's simply nothing to pull.
|
|
54
|
-
async pullRedirects(basePath) {
|
|
55
|
-
const dir = join(basePath, 'redirects');
|
|
56
|
-
let remote;
|
|
57
|
-
try {
|
|
58
|
-
remote = await this.wp.get(SEO_REDIRECTS_ENDPOINT);
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
this.warn(`Skipping redirects: ${error.message}`);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const orphans = await findOrphanedFiles(dir, new Set(remote.map((redirect) => String(redirect.id))), {
|
|
65
|
-
extensions: ['.json'],
|
|
66
|
-
key: numericPrefixKey,
|
|
67
|
-
});
|
|
68
|
-
await this.pullDirectory(dir, remote, orphans, {
|
|
69
|
-
dryRunMessage: `Would pull ${remote.length} redirect(s) to ${dir}`,
|
|
70
|
-
orphanReason: `in ${dir} no longer present on WordPress`,
|
|
71
|
-
pulledMessage: `Pulled ${remote.length} redirect(s) to ${dir}`,
|
|
72
|
-
title: (redirect) => `redirect #${redirect.id}`,
|
|
73
|
-
write: async (redirect, writeDir) => writeFile(join(writeDir, `${redirectFileBase(redirect)}.json`), JSON.stringify(redirect, null, 2) + '\n'),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
async pullSettings(basePath) {
|
|
77
|
-
const file = join(basePath, 'settings.json');
|
|
78
|
-
// `revision` (#234) is never persisted locally, same as RemoteOption.revision: settings.json
|
|
79
|
-
// holds only the settings themselves, exactly as it did before the wrapped response shape.
|
|
80
|
-
const { settings } = await this.wp.get(SEO_SETTINGS_ENDPOINT);
|
|
81
|
-
if (this.dryRun) {
|
|
82
|
-
this.log(`[dry-run] Would pull settings to ${file}`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
await mkdir(basePath, { recursive: true });
|
|
86
|
-
await writeFile(file, JSON.stringify(settings, null, 2) + '\n');
|
|
87
|
-
this.log(`Pulled settings to ${file}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
-
type SeoPushResult = {
|
|
3
|
-
path: string;
|
|
4
|
-
status: 'dry-run' | 'success';
|
|
5
|
-
};
|
|
6
|
-
export default class Push extends PushCommand {
|
|
7
|
-
static args: {
|
|
8
|
-
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
9
|
-
};
|
|
10
|
-
static description: string;
|
|
11
|
-
static enableJsonFlag: boolean;
|
|
12
|
-
static examples: string[];
|
|
13
|
-
static flags: {
|
|
14
|
-
'allow-external-redirects': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
-
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
-
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
-
};
|
|
18
|
-
private allowExternalRedirects;
|
|
19
|
-
run(): Promise<SeoPushResult>;
|
|
20
|
-
private currentPostMetaRevision;
|
|
21
|
-
private jsonFilesIn;
|
|
22
|
-
private pushPostMeta;
|
|
23
|
-
private pushPostMetaFile;
|
|
24
|
-
private pushRedirectFile;
|
|
25
|
-
private pushRedirects;
|
|
26
|
-
private pushSettings;
|
|
27
|
-
private renameToCanonical;
|
|
28
|
-
}
|
|
29
|
-
export {};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import { readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { 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 { getResourceStateProvider } from '../../lib/resource-state.js';
|
|
8
|
-
import { isNotFoundError } from '../../lib/wp-client.js';
|
|
9
|
-
import { pluralize } from '../../utils/pluralize.js';
|
|
10
|
-
import { redirectFileBase, SEO_REDIRECTS_ENDPOINT, SEO_SETTINGS_ENDPOINT, seoPostMetaEndpoint, seoPostMetaItemEndpoint, seoRedirectEndpoint, } from '../../utils/seo-format.js';
|
|
11
|
-
export default class Push extends PushCommand {
|
|
12
|
-
static args = {
|
|
13
|
-
path: Args.string({ description: 'Path to SEO directory (overrides project config)' }),
|
|
14
|
-
};
|
|
15
|
-
static description = 'Push SEO settings, post meta, and redirects to WordPress. Local redirect files created remotely are renamed on disk to the `<id>-<slug>` convention. Fails clearly per file if the active SEO plugin does not support redirects.';
|
|
16
|
-
static enableJsonFlag = true;
|
|
17
|
-
static examples = ['$ lps seo push', '$ lps seo push --allow-external-redirects'];
|
|
18
|
-
static flags = {
|
|
19
|
-
...PushCommand.dryRunFlag,
|
|
20
|
-
...PushCommand.yesFlag,
|
|
21
|
-
'allow-external-redirects': Flags.boolean({
|
|
22
|
-
default: false,
|
|
23
|
-
description: 'Allow pushing a redirect whose target points off this site. Off by default: the server rejects an off-site `urlTo` so a stray push can\'t 301 traffic away.',
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
allowExternalRedirects = false;
|
|
27
|
-
async run() {
|
|
28
|
-
const { args, flags } = await this.parse(Push);
|
|
29
|
-
const { url } = this.siteConfig;
|
|
30
|
-
const path = this.resolveSeoPath(args.path);
|
|
31
|
-
this.allowExternalRedirects = flags['allow-external-redirects'];
|
|
32
|
-
this.log(`Pushing SEO configuration to ${url}`);
|
|
33
|
-
this.log(`SEO path: ${path}`);
|
|
34
|
-
const provider = getResourceStateProvider('seo');
|
|
35
|
-
const beforeState = await this.captureBeforePushState(provider, path);
|
|
36
|
-
await this.pushSettings(path);
|
|
37
|
-
await this.pushPostMeta(path);
|
|
38
|
-
await this.pushRedirects(path);
|
|
39
|
-
await this.writeAfterPushSnapshot(provider, path, beforeState);
|
|
40
|
-
if (this.failedCount > 0) {
|
|
41
|
-
this.error(`${pluralize(this.failedCount, 'SEO item')} failed to push.`);
|
|
42
|
-
}
|
|
43
|
-
if (this.dryRun)
|
|
44
|
-
return { path, status: 'dry-run' };
|
|
45
|
-
await this.recordSuccess();
|
|
46
|
-
this.log('All SEO configuration pushed.');
|
|
47
|
-
return { path, status: 'success' };
|
|
48
|
-
}
|
|
49
|
-
async currentPostMetaRevision(postType, slug) {
|
|
50
|
-
try {
|
|
51
|
-
const current = await this.wp.get(seoPostMetaItemEndpoint(postType, slug));
|
|
52
|
-
return current.revision;
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
if (isNotFoundError(error))
|
|
56
|
-
return undefined;
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async jsonFilesIn(dir) {
|
|
61
|
-
return (await readdirTolerant(dir)).filter((file) => extname(file) === '.json');
|
|
62
|
-
}
|
|
63
|
-
async pushPostMeta(basePath) {
|
|
64
|
-
const root = join(basePath, 'post-meta');
|
|
65
|
-
const postTypeDirs = (await readdirTolerant(root, { withFileTypes: true }))
|
|
66
|
-
.filter((entry) => entry.isDirectory())
|
|
67
|
-
.map((entry) => entry.name);
|
|
68
|
-
for (const postType of postTypeDirs) {
|
|
69
|
-
const dir = join(root, postType);
|
|
70
|
-
const files = await this.jsonFilesIn(dir);
|
|
71
|
-
if (files.length === 0)
|
|
72
|
-
continue;
|
|
73
|
-
this.log(`Found ${pluralize(files.length, postType + ' post-meta file')} to push`);
|
|
74
|
-
await this.runPushTasks(files, (file) => file, async (file, task) => this.pushPostMetaFile(postType, join(dir, file), task));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async pushPostMetaFile(postType, filePath, task) {
|
|
78
|
-
if (this.dryRun) {
|
|
79
|
-
if (task)
|
|
80
|
-
task.output = `[dry-run] Would push: ${filePath}`;
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const post = JSON.parse(await readFile(filePath, 'utf8'));
|
|
85
|
-
// Read the post's current SEO-meta revision right before writing it, and send it back as
|
|
86
|
-
// `expectedRevision`: WordPress refuses the write (412) if something else changed that
|
|
87
|
-
// post's meta in between, instead of this push silently overwriting it (#234). No revision
|
|
88
|
-
// to condition on for a post whose meta doesn't exist remotely yet (an upsert that will
|
|
89
|
-
// itself fail with a clearer "post not found" error): falls back to an unconditional write.
|
|
90
|
-
const expectedRevision = await this.currentPostMetaRevision(postType, post.slug);
|
|
91
|
-
const body = { meta: post.meta, slug: post.slug };
|
|
92
|
-
if (expectedRevision !== undefined)
|
|
93
|
-
body.expectedRevision = expectedRevision;
|
|
94
|
-
await this.wp.post(seoPostMetaEndpoint(postType), body);
|
|
95
|
-
if (task)
|
|
96
|
-
task.output = `Pushed: ${post.slug}`;
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
this.reportTaskFailure(`Failed to push ${filePath}: ${error.message}`, error, task);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async pushRedirectFile(filePath, task) {
|
|
103
|
-
if (this.dryRun) {
|
|
104
|
-
if (task)
|
|
105
|
-
task.output = `[dry-run] Would push: ${filePath}`;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
const redirect = JSON.parse(await readFile(filePath, 'utf8'));
|
|
110
|
-
const payload = {
|
|
111
|
-
headerCode: redirect.headerCode,
|
|
112
|
-
sources: redirect.sources,
|
|
113
|
-
status: redirect.status,
|
|
114
|
-
urlTo: redirect.urlTo,
|
|
115
|
-
...(this.allowExternalRedirects && { allowExternal: true }),
|
|
116
|
-
};
|
|
117
|
-
// The id recorded locally may not exist on this site (e.g. a fresh install): putOrCreate
|
|
118
|
-
// falls back to POST instead of failing, adopting whatever id the site assigns.
|
|
119
|
-
const { body, created } = await putOrCreate(this.wp, {
|
|
120
|
-
id: redirect.id ?? null,
|
|
121
|
-
payload,
|
|
122
|
-
postEndpoint: SEO_REDIRECTS_ENDPOINT,
|
|
123
|
-
putEndpoint: (id) => seoRedirectEndpoint(id),
|
|
124
|
-
});
|
|
125
|
-
if (created)
|
|
126
|
-
await this.renameToCanonical(filePath, body);
|
|
127
|
-
if (task)
|
|
128
|
-
task.output = `Pushed: redirect #${created ? body.id : redirect.id}`;
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
this.reportTaskFailure(`Failed to push ${filePath}: ${error.message}`, error, task);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async pushRedirects(basePath) {
|
|
135
|
-
const dir = join(basePath, 'redirects');
|
|
136
|
-
const files = await this.jsonFilesIn(dir);
|
|
137
|
-
if (files.length === 0)
|
|
138
|
-
return;
|
|
139
|
-
this.log(`Found ${pluralize(files.length, 'redirect')} to push`);
|
|
140
|
-
await this.runPushTasks(files, (file) => file, async (file, task) => this.pushRedirectFile(join(dir, file), task));
|
|
141
|
-
}
|
|
142
|
-
async pushSettings(basePath) {
|
|
143
|
-
const file = join(basePath, 'settings.json');
|
|
144
|
-
let raw;
|
|
145
|
-
try {
|
|
146
|
-
raw = await readFile(file, 'utf8');
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
if (error.code === 'ENOENT')
|
|
150
|
-
return;
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
if (this.dryRun) {
|
|
154
|
-
this.log(`[dry-run] Would push: ${file}`);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
const settings = JSON.parse(raw);
|
|
159
|
-
// Read the settings' current revision right before writing them, and send it back as
|
|
160
|
-
// `expectedRevision`: WordPress refuses the write (412) if something else changed the
|
|
161
|
-
// settings in between, instead of this push silently overwriting it (#234).
|
|
162
|
-
const { revision } = await this.wp.get(SEO_SETTINGS_ENDPOINT);
|
|
163
|
-
await this.wp.put(SEO_SETTINGS_ENDPOINT, { expectedRevision: revision, settings });
|
|
164
|
-
this.log(`Pushed: ${file}`);
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
this.failedCount++;
|
|
168
|
-
this.warn(`Failed to push ${file}: ${error.message}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async renameToCanonical(filePath, redirect) {
|
|
172
|
-
const dir = dirname(filePath);
|
|
173
|
-
const canonicalPath = join(dir, `${redirectFileBase(redirect)}.json`);
|
|
174
|
-
await writeFile(canonicalPath, JSON.stringify(redirect, null, 2) + '\n');
|
|
175
|
-
if (canonicalPath !== filePath)
|
|
176
|
-
await rm(filePath, { force: true });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { resourceRollbackCommand } from '../../lib/rollback-command.js';
|
|
2
|
-
export default resourceRollbackCommand('seo', {
|
|
3
|
-
description: 'Restore SEO settings, post meta, and redirects to a snapshot taken automatically before an earlier `lps seo push`',
|
|
4
|
-
pathNoun: 'SEO directory',
|
|
5
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export declare const DEFAULT_POST_TYPES: readonly ["post", "page"];
|
|
2
|
-
export declare const SEO_SETTINGS_ENDPOINT = "loopress/v1/seo/settings";
|
|
3
|
-
export declare const SEO_REDIRECTS_ENDPOINT = "loopress/v1/seo/redirects";
|
|
4
|
-
export declare function seoRedirectEndpoint(id: number): string;
|
|
5
|
-
export declare function seoPostMetaEndpoint(postType: string): string;
|
|
6
|
-
export declare function seoPostMetaItemEndpoint(postType: string, slug: string): string;
|
|
7
|
-
export type SeoPostMeta = {
|
|
8
|
-
meta: Record<string, unknown>;
|
|
9
|
-
slug: string;
|
|
10
|
-
title: string;
|
|
11
|
-
};
|
|
12
|
-
export type RemoteSeoPostMeta = SeoPostMeta & {
|
|
13
|
-
revision: string;
|
|
14
|
-
};
|
|
15
|
-
export type RemoteSeoSettings = {
|
|
16
|
-
revision: string;
|
|
17
|
-
settings: Record<string, unknown>;
|
|
18
|
-
};
|
|
19
|
-
export type SeoRedirect = {
|
|
20
|
-
createdAt: null | string;
|
|
21
|
-
headerCode: number;
|
|
22
|
-
hits: number;
|
|
23
|
-
id: number;
|
|
24
|
-
sources: unknown;
|
|
25
|
-
status: string;
|
|
26
|
-
updatedAt: null | string;
|
|
27
|
-
urlTo: string;
|
|
28
|
-
};
|
|
29
|
-
export declare function redirectFileBase(redirect: SeoRedirect): string;
|
package/dist/utils/seo-format.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { toSlug } from './to-slug.js';
|
|
2
|
-
// Post types synced by `lps seo pull` when --post-type isn't given.
|
|
3
|
-
export const DEFAULT_POST_TYPES = ['post', 'page'];
|
|
4
|
-
export const SEO_SETTINGS_ENDPOINT = 'loopress/v1/seo/settings';
|
|
5
|
-
export const SEO_REDIRECTS_ENDPOINT = 'loopress/v1/seo/redirects';
|
|
6
|
-
export function seoRedirectEndpoint(id) {
|
|
7
|
-
return `${SEO_REDIRECTS_ENDPOINT}/${id}`;
|
|
8
|
-
}
|
|
9
|
-
export function seoPostMetaEndpoint(postType) {
|
|
10
|
-
return `loopress/v1/seo/post-meta/${postType}`;
|
|
11
|
-
}
|
|
12
|
-
export function seoPostMetaItemEndpoint(postType, slug) {
|
|
13
|
-
return `${seoPostMetaEndpoint(postType)}/${encodeURIComponent(slug)}`;
|
|
14
|
-
}
|
|
15
|
-
// The `<id>-<slug>.json` on-disk convention shared by `seo pull` and `seo push`.
|
|
16
|
-
export function redirectFileBase(redirect) {
|
|
17
|
-
return `${redirect.id}-${toSlug(redirect.urlTo, 'redirect')}`;
|
|
18
|
-
}
|