@loopress/cli 0.34.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.
Files changed (44) hide show
  1. package/README.md +190 -174
  2. package/dist/commands/doctor.js +2 -2
  3. package/dist/commands/form/push.js +3 -2
  4. package/dist/commands/option/list.js +6 -1
  5. package/dist/commands/plugin/audit.d.ts +1 -1
  6. package/dist/commands/plugin/audit.js +45 -23
  7. package/dist/commands/plugin/outdated.d.ts +22 -0
  8. package/dist/commands/plugin/outdated.js +67 -0
  9. package/dist/commands/plugin/push.d.ts +2 -1
  10. package/dist/commands/plugin/push.js +70 -19
  11. package/dist/commands/plugin/update.d.ts +36 -0
  12. package/dist/commands/plugin/update.js +121 -0
  13. package/dist/commands/push.js +12 -1
  14. package/dist/commands/snippet/push.js +3 -2
  15. package/dist/lib/base.d.ts +1 -0
  16. package/dist/lib/base.js +22 -1
  17. package/dist/lib/capabilities.d.ts +5 -0
  18. package/dist/lib/capabilities.js +16 -0
  19. package/dist/lib/load-snippets.js +1 -4
  20. package/dist/lib/push-command.d.ts +22 -0
  21. package/dist/lib/push-command.js +41 -11
  22. package/dist/lib/validate-local.js +4 -4
  23. package/dist/lib/wp-client.d.ts +1 -0
  24. package/dist/lib/wp-client.js +30 -10
  25. package/dist/types/plugin.d.ts +2 -1
  26. package/dist/types/plugin.js +32 -1
  27. package/dist/utils/option-format.d.ts +1 -0
  28. package/dist/utils/option-format.js +14 -1
  29. package/dist/utils/plugins.d.ts +3 -0
  30. package/dist/utils/plugins.js +6 -1
  31. package/dist/utils/rename-retry.d.ts +1 -0
  32. package/dist/utils/rename-retry.js +20 -0
  33. package/dist/utils/version.d.ts +2 -0
  34. package/dist/utils/version.js +21 -0
  35. package/dist/utils/wporg.d.ts +5 -0
  36. package/dist/utils/wporg.js +41 -0
  37. package/oclif.manifest.json +1401 -1301
  38. package/package.json +10 -10
  39. package/dist/commands/api/publish.d.ts +0 -9
  40. package/dist/commands/api/publish.js +0 -45
  41. package/dist/commands/snippet/publish.d.ts +0 -10
  42. package/dist/commands/snippet/publish.js +0 -62
  43. package/dist/lib/resolve-linked-project.d.ts +0 -8
  44. 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.34.0",
4
+ "version": "0.35.0",
5
5
  "author": "jean-smaug",
6
6
  "bin": {
7
7
  "loopress": "bin/run.js",
@@ -12,21 +12,21 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@inquirer/prompts": "^8.7.5",
15
- "@oclif/core": "^5.1.9",
15
+ "@oclif/core": "^5.1.11",
16
16
  "@oclif/plugin-help": "^7.0.6",
17
17
  "@oclif/plugin-warn-if-update-available": "^4.0.5",
18
- "@sentry/node": "^11.9.0",
18
+ "@sentry/node": "^11.11.0",
19
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.1.0",
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": "^5.0.0",
29
+ "tty-table": "^7.2.1",
30
30
  "write-file-atomic": "^8.0.0",
31
31
  "zod": "^4.6.7"
32
32
  },
@@ -34,14 +34,14 @@
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.5",
37
+ "@types/node": "26.6.6",
38
38
  "@types/write-file-atomic": "^4.0.3",
39
- "@vitest/coverage-v8": "^5.0.3",
39
+ "@vitest/coverage-v8": "^5.1.0",
40
40
  "@vitest/eslint-plugin": "^1.6.28",
41
- "eslint": "^10.13.0",
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.1",
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
47
  "oclif": "^6.0.15",
@@ -50,7 +50,7 @@
50
50
  "ts-node": "10.9.2",
51
51
  "tsx": "4.23.15",
52
52
  "typescript": "^6.0.3",
53
- "vitest": "^5.0.3"
53
+ "vitest": "^5.1.0"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=22.0.0"
@@ -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
- }