@loopress/cli 0.33.0 → 0.35.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 +236 -207
- package/dist/commands/app/push.js +2 -1
- package/dist/commands/block/push.js +9 -4
- package/dist/commands/dev.d.ts +2 -2
- package/dist/commands/dev.js +10 -16
- package/dist/commands/diff.d.ts +6 -4
- package/dist/commands/diff.js +49 -29
- package/dist/commands/doctor.js +13 -3
- package/dist/commands/form/push.js +3 -2
- package/dist/commands/option/list.js +6 -1
- package/dist/commands/option/push.js +1 -0
- package/dist/commands/page/push.js +1 -0
- package/dist/commands/plugin/add.d.ts +1 -0
- package/dist/commands/plugin/add.js +50 -9
- package/dist/commands/plugin/audit.d.ts +1 -1
- package/dist/commands/plugin/audit.js +45 -23
- package/dist/commands/plugin/outdated.d.ts +22 -0
- package/dist/commands/plugin/outdated.js +67 -0
- package/dist/commands/plugin/push.d.ts +2 -1
- package/dist/commands/plugin/push.js +109 -23
- package/dist/commands/plugin/update.d.ts +36 -0
- package/dist/commands/plugin/update.js +121 -0
- package/dist/commands/project/config.js +0 -1
- package/dist/commands/promote.d.ts +1 -0
- package/dist/commands/promote.js +8 -4
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/pull.js +26 -1
- package/dist/commands/push.d.ts +9 -0
- package/dist/commands/push.js +116 -40
- package/dist/commands/snippet/push.js +3 -2
- package/dist/commands/theme/style/diff.js +1 -1
- package/dist/commands/theme/style/push.js +1 -1
- package/dist/commands/theme/style/rollback.js +1 -1
- package/dist/commands/theme/template/push.d.ts +2 -0
- package/dist/commands/theme/template/push.js +42 -13
- package/dist/commands/validate.js +3 -0
- package/dist/lib/base.d.ts +7 -0
- package/dist/lib/base.js +55 -2
- package/dist/lib/capabilities.d.ts +5 -0
- package/dist/lib/capabilities.js +16 -0
- package/dist/lib/declared-plugins.d.ts +9 -0
- package/dist/lib/declared-plugins.js +21 -0
- package/dist/lib/declared-type-command.js +3 -0
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +11 -13
- package/dist/lib/diff-command.d.ts +17 -0
- package/dist/lib/diff-command.js +125 -9
- package/dist/lib/environment-urls.d.ts +6 -0
- package/dist/lib/environment-urls.js +59 -0
- package/dist/lib/exclude.d.ts +16 -0
- package/dist/lib/exclude.js +102 -0
- package/dist/lib/load-snippets.js +3 -5
- package/dist/lib/php-files-command.js +5 -3
- package/dist/lib/push-command.d.ts +26 -0
- package/dist/lib/push-command.js +78 -12
- package/dist/lib/readdir-tolerant.js +6 -3
- package/dist/lib/resource-selection.d.ts +9 -0
- package/dist/lib/resource-selection.js +49 -0
- package/dist/lib/resource-state.d.ts +2 -0
- package/dist/lib/resource-state.js +25 -4
- package/dist/lib/rollback-command.js +1 -1
- package/dist/lib/rollback-materialize.js +1 -1
- package/dist/lib/validate-local.d.ts +1 -0
- package/dist/lib/validate-local.js +71 -12
- package/dist/lib/wp-client.d.ts +2 -0
- package/dist/lib/wp-client.js +36 -10
- package/dist/types/plugin.d.ts +2 -1
- package/dist/types/plugin.js +32 -1
- package/dist/types/project-config.generated.d.ts +15 -4
- package/dist/utils/block-format.d.ts +2 -0
- package/dist/utils/block-format.js +18 -2
- package/dist/utils/option-format.d.ts +1 -0
- package/dist/utils/option-format.js +14 -1
- package/dist/utils/page-format.js +2 -1
- package/dist/utils/plugins.d.ts +3 -0
- package/dist/utils/plugins.js +6 -1
- package/dist/utils/rename-retry.d.ts +1 -0
- package/dist/utils/rename-retry.js +20 -0
- package/dist/utils/resource-dirs.js +4 -4
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.js +21 -0
- package/dist/utils/wporg.d.ts +5 -0
- package/dist/utils/wporg.js +41 -0
- package/oclif.manifest.json +2131 -1150
- package/package.json +14 -14
- package/schemas/project-config.schema.json +21 -4
- package/dist/commands/api/publish.d.ts +0 -9
- package/dist/commands/api/publish.js +0 -45
- package/dist/commands/snippet/publish.d.ts +0 -10
- package/dist/commands/snippet/publish.js +0 -62
- package/dist/lib/resolve-linked-project.d.ts +0 -8
- package/dist/lib/resolve-linked-project.js +0 -27
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopress/cli",
|
|
3
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.
|
|
4
|
+
"version": "0.35.0",
|
|
5
5
|
"author": "jean-smaug",
|
|
6
6
|
"bin": {
|
|
7
7
|
"loopress": "bin/run.js",
|
|
@@ -11,22 +11,22 @@
|
|
|
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.
|
|
14
|
+
"@inquirer/prompts": "^8.7.5",
|
|
15
|
+
"@oclif/core": "^5.1.11",
|
|
16
|
+
"@oclif/plugin-help": "^7.0.6",
|
|
17
17
|
"@oclif/plugin-warn-if-update-available": "^4.0.5",
|
|
18
|
-
"@sentry/node": "^11.
|
|
19
|
-
"chokidar": "^
|
|
18
|
+
"@sentry/node": "^11.11.0",
|
|
19
|
+
"chokidar": "^6.0.0",
|
|
20
20
|
"compare-versions": "^6.1.1",
|
|
21
21
|
"diff": "^9.0.0",
|
|
22
22
|
"esbuild": "0.28.2",
|
|
23
|
-
"got": "^16.
|
|
23
|
+
"got": "^16.2.0",
|
|
24
24
|
"listr2": "^11.4.0",
|
|
25
25
|
"microdiff": "^1.6.0",
|
|
26
26
|
"open": "^11.0.4",
|
|
27
27
|
"playwright-core": "^1.64.1",
|
|
28
28
|
"slugify": "^1.6.9",
|
|
29
|
-
"tty-table": "^
|
|
29
|
+
"tty-table": "^7.2.1",
|
|
30
30
|
"write-file-atomic": "^8.0.0",
|
|
31
31
|
"zod": "^4.6.7"
|
|
32
32
|
},
|
|
@@ -34,23 +34,23 @@
|
|
|
34
34
|
"@oclif/prettier-config": "^0.2.2",
|
|
35
35
|
"@stryker-mutator/core": "^10.1.1",
|
|
36
36
|
"@stryker-mutator/vitest-runner": "^10.1.1",
|
|
37
|
-
"@types/node": "26.6.
|
|
37
|
+
"@types/node": "26.6.6",
|
|
38
38
|
"@types/write-file-atomic": "^4.0.3",
|
|
39
|
-
"@vitest/coverage-v8": "^
|
|
39
|
+
"@vitest/coverage-v8": "^5.1.0",
|
|
40
40
|
"@vitest/eslint-plugin": "^1.6.28",
|
|
41
|
-
"eslint": "^10.
|
|
41
|
+
"eslint": "^10.14.0",
|
|
42
42
|
"eslint-config-oclif": "^7.1.10",
|
|
43
43
|
"eslint-config-prettier": "^10",
|
|
44
|
-
"eslint-plugin-regexp": "^3.3.
|
|
44
|
+
"eslint-plugin-regexp": "^3.3.3",
|
|
45
45
|
"eslint-plugin-sonarjs": "^4.2.2",
|
|
46
46
|
"json-schema-to-typescript": "^16.0.0",
|
|
47
|
-
"oclif": "^6.0.
|
|
47
|
+
"oclif": "^6.0.15",
|
|
48
48
|
"prettier": "3.9.10",
|
|
49
49
|
"shx": "^0.4.0",
|
|
50
50
|
"ts-node": "10.9.2",
|
|
51
51
|
"tsx": "4.23.15",
|
|
52
52
|
"typescript": "^6.0.3",
|
|
53
|
-
"vitest": "^
|
|
53
|
+
"vitest": "^5.1.0"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
56
56
|
"node": ">=22.0.0"
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"description": "Directory of dynamic blocks, one <slug>/ folder holding block.json and render.php, registered as loopress/<slug>, relative to rootDir.",
|
|
40
40
|
"default": "blocks"
|
|
41
41
|
},
|
|
42
|
-
"
|
|
42
|
+
"hookDir": {
|
|
43
43
|
"type": "string",
|
|
44
44
|
"description": "Directory where hook files (WordPress actions, filters, and scheduled tasks) are read from and written to, relative to rootDir.",
|
|
45
45
|
"default": "hooks"
|
|
46
46
|
},
|
|
47
|
-
"
|
|
47
|
+
"appDir": {
|
|
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"
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"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.",
|
|
75
75
|
"default": "theme/blocks"
|
|
76
76
|
},
|
|
77
|
-
"
|
|
77
|
+
"optionDir": {
|
|
78
78
|
"type": "string",
|
|
79
79
|
"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.",
|
|
80
80
|
"default": "options"
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"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.",
|
|
110
110
|
"default": "menus"
|
|
111
111
|
},
|
|
112
|
-
"
|
|
112
|
+
"themeStyleDir": {
|
|
113
113
|
"type": "string",
|
|
114
114
|
"description": "Directory where the active block theme's Global Styles customizations (Site Editor > Styles: colors, typography, spacing) are read from and written to, relative to rootDir. Classic (non-block) themes are not supported.",
|
|
115
115
|
"default": "theme"
|
|
@@ -143,6 +143,23 @@
|
|
|
143
143
|
}
|
|
144
144
|
]
|
|
145
145
|
},
|
|
146
|
+
"environments": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"description": "Per-environment settings, keyed by environment name (as in `lps project config`).",
|
|
149
|
+
"additionalProperties": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"properties": {
|
|
153
|
+
"exclude": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"description": "Globs relative to rootDir (`*`, `**`, `?`) of files this environment never receives. `lps push`, `lps diff` and `lps promote` leave them alone on it: never pushed, never pruned or mirrored away, reported as held back instead of drift. A glob matching a directory covers everything inside. `lps pull` and `lps dev` ignore it.",
|
|
156
|
+
"items": {"type": "string"},
|
|
157
|
+
"examples": [["cpt/vocabulary-games.json", "theme/templates/*-vocabulary-games.html"]]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"examples": [{"production": {"exclude": ["hooks/vocabulary-games-page.php"]}}]
|
|
162
|
+
},
|
|
146
163
|
"activateChildTheme": {
|
|
147
164
|
"type": "boolean",
|
|
148
165
|
"description": "Keep the Loopress child theme (<parent>-loopress) active: `lps theme template push`, and so `lps push`, activates it when it isn't. Off by default, a theme switch changes the settings WordPress stores per theme.",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class Publish extends Command {
|
|
3
|
-
static args: {
|
|
4
|
-
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
-
};
|
|
6
|
-
static description: string;
|
|
7
|
-
static examples: string[];
|
|
8
|
-
run(): Promise<void>;
|
|
9
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Args, Command } from '@oclif/core';
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
3
|
-
import { ApiClient } from '../../lib/api-client.js';
|
|
4
|
-
import { loadFiles } from '../../lib/load-files.js';
|
|
5
|
-
import { errorMessage } from '../../lib/narrow.js';
|
|
6
|
-
import { resolveLinkedProject } from '../../lib/resolve-linked-project.js';
|
|
7
|
-
import { pluralize } from '../../utils/pluralize.js';
|
|
8
|
-
// Publishes to the Loopress api (not a WordPress site), so this does not extend
|
|
9
|
-
// `LoopressCommand`/`PushCommand`: those force an environment to be resolved, but publishing
|
|
10
|
-
// for cross-project sharing doesn't need one, same reasoning as `snippet publish`. An API route
|
|
11
|
-
// is a special case of the same publish/deploy rules as snippets and ACF (see
|
|
12
|
-
// obsidian/Product/API and Console Backlog.md, US-18): a `push` (direct to WordPress, this
|
|
13
|
-
// project's own site) and a `publish` (to the cloud, deployable to other projects) are two
|
|
14
|
-
// separate, coexisting paths.
|
|
15
|
-
export default class Publish extends Command {
|
|
16
|
-
static args = {
|
|
17
|
-
path: Args.string({ description: 'Path to api directory (overrides project config)' }),
|
|
18
|
-
};
|
|
19
|
-
static description = 'Publish custom API routes to your Loopress account so they can be deployed to other projects. Does not touch any WordPress site.';
|
|
20
|
-
static examples = ['$ lps api publish', '$ lps api publish ./api'];
|
|
21
|
-
async run() {
|
|
22
|
-
const { args } = await this.parse(Publish);
|
|
23
|
-
const { apiProjectId, localConfig, token } = await resolveLinkedProject((message) => this.error(message));
|
|
24
|
-
const path = args.path ?? join(localConfig.rootDir ?? '.', localConfig.apiDir ?? 'api');
|
|
25
|
-
this.log(`Publishing API routes from ${path}`);
|
|
26
|
-
const files = await loadFiles(path, {
|
|
27
|
-
extension: '.php',
|
|
28
|
-
onSkip: (message) => { this.warn(message); },
|
|
29
|
-
parse: (raw, filePath) => ({ code: raw, filename: basename(filePath, '.php') }),
|
|
30
|
-
});
|
|
31
|
-
const api = new ApiClient(token);
|
|
32
|
-
try {
|
|
33
|
-
await api.post(`projects/${apiProjectId}/api-routes/publish/upsert`, {
|
|
34
|
-
routes: files,
|
|
35
|
-
});
|
|
36
|
-
await api.post(`projects/${apiProjectId}/api-routes/publish/prune`, {
|
|
37
|
-
filenames: files.map((file) => file.filename),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
this.error(errorMessage(error));
|
|
42
|
-
}
|
|
43
|
-
this.log(`Published ${pluralize(files.length, 'route')} to your Loopress account.`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class Publish extends Command {
|
|
3
|
-
static args: {
|
|
4
|
-
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
-
};
|
|
6
|
-
static description: string;
|
|
7
|
-
static examples: string[];
|
|
8
|
-
run(): Promise<void>;
|
|
9
|
-
private toPayload;
|
|
10
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Args, Command } from '@oclif/core';
|
|
2
|
-
import { ApiClient } from '../../lib/api-client.js';
|
|
3
|
-
import { loadSnippets } from '../../lib/load-snippets.js';
|
|
4
|
-
import { errorMessage } from '../../lib/narrow.js';
|
|
5
|
-
import { resolveLinkedProject } from '../../lib/resolve-linked-project.js';
|
|
6
|
-
import {} from '../../types/snippet.js';
|
|
7
|
-
import { pluralize } from '../../utils/pluralize.js';
|
|
8
|
-
import { resolveResourceDir } from '../../utils/resource-dirs.js';
|
|
9
|
-
import { toSlug } from '../../utils/to-slug.js';
|
|
10
|
-
// Publishes to the Loopress api (not a WordPress site), so this does not extend
|
|
11
|
-
// `LoopressCommand`/`PushCommand`: those force an environment to be resolved, but a project
|
|
12
|
-
// publishing its snippets for sharing doesn't need one, a pure "library" project
|
|
13
|
-
// may have zero environments configured at all.
|
|
14
|
-
export default class Publish extends Command {
|
|
15
|
-
static args = {
|
|
16
|
-
path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
|
|
17
|
-
};
|
|
18
|
-
static description = 'Publish snippets to your Loopress account so they can be deployed to other projects. Does not touch any WordPress site.';
|
|
19
|
-
static examples = ['$ lps snippet publish', '$ lps snippet publish ./snippets'];
|
|
20
|
-
async run() {
|
|
21
|
-
const { args } = await this.parse(Publish);
|
|
22
|
-
const { apiProjectId, localConfig, token } = await resolveLinkedProject((message) => this.error(message));
|
|
23
|
-
const path = resolveResourceDir('snippets', localConfig, args.path);
|
|
24
|
-
this.log(`Publishing snippets from ${path}`);
|
|
25
|
-
let snippets;
|
|
26
|
-
try {
|
|
27
|
-
snippets = await loadSnippets(path, (message) => {
|
|
28
|
-
this.warn(message);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
this.error(errorMessage(error));
|
|
33
|
-
}
|
|
34
|
-
const api = new ApiClient(token);
|
|
35
|
-
try {
|
|
36
|
-
await api.post(`projects/${apiProjectId}/snippets/publish`, {
|
|
37
|
-
snippets: snippets.map((snippet) => this.toPayload(snippet)),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
this.error(errorMessage(error));
|
|
42
|
-
}
|
|
43
|
-
this.log(`Published ${pluralize(snippets.length, 'snippet')} to your Loopress account.`);
|
|
44
|
-
}
|
|
45
|
-
toPayload(snippet) {
|
|
46
|
-
return {
|
|
47
|
-
active: snippet.active,
|
|
48
|
-
code: snippet.code,
|
|
49
|
-
insertMethod: snippet.insertMethod,
|
|
50
|
-
location: snippet.location,
|
|
51
|
-
name: snippet.name,
|
|
52
|
-
priority: snippet.priority,
|
|
53
|
-
shortcodeAttributes: snippet.shortcodeAttributes,
|
|
54
|
-
// Derived from the snippet's name rather than its on-disk filename: the same
|
|
55
|
-
// slugification `push`/`pull` already use for the canonical `<id>-<slug>` filename
|
|
56
|
-
// convention, so it stays stable even for a not-yet-pushed file with an arbitrary name.
|
|
57
|
-
slug: toSlug(snippet.name),
|
|
58
|
-
tags: snippet.tags,
|
|
59
|
-
type: snippet.type,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type LoopressLocalConfig } from '../utils/loopress-config.js';
|
|
2
|
-
type LinkedProject = {
|
|
3
|
-
apiProjectId: string;
|
|
4
|
-
localConfig: LoopressLocalConfig;
|
|
5
|
-
token: string;
|
|
6
|
-
};
|
|
7
|
-
export declare function resolveLinkedProject(fail: (message: string) => never): Promise<LinkedProject>;
|
|
8
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { authManager } from '../config/auth.manager.js';
|
|
2
|
-
import { configManager } from '../config/project-config.manager.js';
|
|
3
|
-
import { readLocalConfig } from '../utils/loopress-config.js';
|
|
4
|
-
// Shared preamble for `api publish` and `snippet publish`. Both target the Loopress cloud (never
|
|
5
|
-
// a WordPress site), so neither extends `LoopressCommand`/`PushCommand` and neither needs an
|
|
6
|
-
// environment resolved, but both need a logged-in user and a project already linked to the
|
|
7
|
-
// account (`lps project push`). `fail` is the caller's `this.error`, so its messages keep
|
|
8
|
-
// oclif's formatting and exit code; typed `=> never` so callers still narrow after the guards.
|
|
9
|
-
export async function resolveLinkedProject(fail) {
|
|
10
|
-
const token = process.env.LOOPRESS_TOKEN ?? authManager.getAuth()?.token;
|
|
11
|
-
if (!token) {
|
|
12
|
-
fail('Not logged in. Run `lps login` first.');
|
|
13
|
-
}
|
|
14
|
-
const localConfig = await readLocalConfig();
|
|
15
|
-
const projectId = localConfig.projectId ?? configManager.getCurrentProject()?.id;
|
|
16
|
-
if (!projectId) {
|
|
17
|
-
fail('No project configured. Run `lps project config` first.');
|
|
18
|
-
}
|
|
19
|
-
const project = configManager.getProject(projectId);
|
|
20
|
-
if (!project) {
|
|
21
|
-
fail(`Project "${projectId}" (from loopress.json) not found. Run \`lps project config\` to configure it.`);
|
|
22
|
-
}
|
|
23
|
-
if (!project.apiProjectId) {
|
|
24
|
-
fail(`Project "${project.name}" is not linked to your Loopress account yet. Run \`lps project push\` first.`);
|
|
25
|
-
}
|
|
26
|
-
return { apiProjectId: project.apiProjectId, localConfig, token };
|
|
27
|
-
}
|