@loopress/cli 0.29.0 → 0.30.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 (132) hide show
  1. package/README.md +1301 -345
  2. package/dist/commands/cpt/diff.js +5 -0
  3. package/dist/commands/cpt/list.d.ts +2 -0
  4. package/dist/commands/cpt/list.js +4 -0
  5. package/dist/commands/cpt/pull.d.ts +2 -0
  6. package/dist/commands/cpt/pull.js +4 -0
  7. package/dist/commands/cpt/push.d.ts +2 -0
  8. package/dist/commands/cpt/push.js +4 -0
  9. package/dist/commands/cpt/rm.d.ts +2 -0
  10. package/dist/commands/cpt/rm.js +3 -0
  11. package/dist/commands/cpt/rollback.js +5 -0
  12. package/dist/commands/dev.js +14 -4
  13. package/dist/commands/diff.js +1 -1
  14. package/dist/commands/doctor.d.ts +1 -0
  15. package/dist/commands/doctor.js +30 -2
  16. package/dist/commands/meta/diff.d.ts +2 -0
  17. package/dist/commands/meta/diff.js +5 -0
  18. package/dist/commands/meta/list.d.ts +19 -0
  19. package/dist/commands/meta/list.js +21 -0
  20. package/dist/commands/meta/pull.d.ts +2 -0
  21. package/dist/commands/meta/pull.js +4 -0
  22. package/dist/commands/meta/push.d.ts +2 -0
  23. package/dist/commands/meta/push.js +4 -0
  24. package/dist/commands/meta/rm.d.ts +2 -0
  25. package/dist/commands/meta/rm.js +3 -0
  26. package/dist/commands/meta/rollback.d.ts +2 -0
  27. package/dist/commands/meta/rollback.js +5 -0
  28. package/dist/commands/option/list.js +2 -8
  29. package/dist/commands/pattern/diff.d.ts +2 -0
  30. package/dist/commands/pattern/diff.js +5 -0
  31. package/dist/commands/pattern/list.d.ts +2 -0
  32. package/dist/commands/pattern/list.js +3 -0
  33. package/dist/commands/pattern/pull.d.ts +2 -0
  34. package/dist/commands/pattern/pull.js +4 -0
  35. package/dist/commands/pattern/push.d.ts +2 -0
  36. package/dist/commands/pattern/push.js +4 -0
  37. package/dist/commands/pattern/rm.d.ts +2 -0
  38. package/dist/commands/pattern/rm.js +3 -0
  39. package/dist/commands/pattern/rollback.d.ts +2 -0
  40. package/dist/commands/pattern/rollback.js +5 -0
  41. package/dist/commands/plugin/audit.js +2 -1
  42. package/dist/commands/project/config.d.ts +1 -0
  43. package/dist/commands/project/config.js +20 -1
  44. package/dist/commands/project/list.d.ts +1 -1
  45. package/dist/commands/project/list.js +6 -6
  46. package/dist/commands/project/pull.js +11 -8
  47. package/dist/commands/project/push.js +23 -18
  48. package/dist/commands/project/remove.js +1 -1
  49. package/dist/commands/project/switch.d.ts +0 -1
  50. package/dist/commands/project/switch.js +16 -36
  51. package/dist/commands/pull.js +7 -2
  52. package/dist/commands/push.js +9 -2
  53. package/dist/commands/role/diff.d.ts +2 -0
  54. package/dist/commands/role/diff.js +5 -0
  55. package/dist/commands/role/list.d.ts +2 -0
  56. package/dist/commands/role/list.js +4 -0
  57. package/dist/commands/role/pull.d.ts +2 -0
  58. package/dist/commands/role/pull.js +4 -0
  59. package/dist/commands/role/push.d.ts +2 -0
  60. package/dist/commands/role/push.js +4 -0
  61. package/dist/commands/role/rm.d.ts +2 -0
  62. package/dist/commands/role/rm.js +3 -0
  63. package/dist/commands/role/rollback.d.ts +2 -0
  64. package/dist/commands/role/rollback.js +5 -0
  65. package/dist/commands/status.d.ts +2 -3
  66. package/dist/commands/status.js +21 -53
  67. package/dist/commands/taxonomy/diff.d.ts +2 -0
  68. package/dist/commands/taxonomy/diff.js +5 -0
  69. package/dist/commands/taxonomy/list.d.ts +2 -0
  70. package/dist/commands/taxonomy/list.js +5 -0
  71. package/dist/commands/taxonomy/pull.d.ts +2 -0
  72. package/dist/commands/taxonomy/pull.js +4 -0
  73. package/dist/commands/taxonomy/push.d.ts +2 -0
  74. package/dist/commands/taxonomy/push.js +4 -0
  75. package/dist/commands/taxonomy/rm.d.ts +2 -0
  76. package/dist/commands/taxonomy/rm.js +3 -0
  77. package/dist/commands/taxonomy/rollback.d.ts +2 -0
  78. package/dist/commands/taxonomy/rollback.js +5 -0
  79. package/dist/commands/theme/template/push.d.ts +2 -0
  80. package/dist/commands/theme/template/push.js +26 -5
  81. package/dist/config/project-config.manager.d.ts +3 -3
  82. package/dist/config/project-config.manager.js +22 -31
  83. package/dist/hooks/finally.js +11 -3
  84. package/dist/lib/api-client.js +2 -1
  85. package/dist/lib/app-build.js +3 -1
  86. package/dist/lib/base.d.ts +1 -2
  87. package/dist/lib/base.js +30 -38
  88. package/dist/lib/declared-type-command.d.ts +35 -0
  89. package/dist/lib/declared-type-command.js +192 -0
  90. package/dist/lib/dev-watch.d.ts +1 -1
  91. package/dist/lib/dev-watch.js +7 -3
  92. package/dist/lib/github-release.js +4 -0
  93. package/dist/lib/local-callback-server.js +3 -1
  94. package/dist/lib/push-command.d.ts +1 -0
  95. package/dist/lib/push-command.js +31 -10
  96. package/dist/lib/put-or-create.js +3 -4
  97. package/dist/lib/resource-state.js +33 -80
  98. package/dist/lib/rollback-materialize.js +13 -24
  99. package/dist/lib/spinner.d.ts +2 -0
  100. package/dist/lib/spinner.js +72 -0
  101. package/dist/lib/temp-admin.d.ts +10 -1
  102. package/dist/lib/temp-admin.js +27 -2
  103. package/dist/lib/validate-local.js +31 -9
  104. package/dist/lib/wp-client.d.ts +1 -0
  105. package/dist/lib/wp-client.js +7 -1
  106. package/dist/lib/wp-site-diagnostic.js +3 -2
  107. package/dist/types/global-config.generated.d.ts +1 -5
  108. package/dist/types/project-config.generated.d.ts +28 -4
  109. package/dist/utils/composer.js +2 -1
  110. package/dist/utils/declared-type-format.d.ts +52 -0
  111. package/dist/utils/declared-type-format.js +233 -0
  112. package/dist/utils/resource-dirs.d.ts +9 -3
  113. package/dist/utils/resource-dirs.js +16 -4
  114. package/dist/utils/template-format.d.ts +1 -0
  115. package/dist/utils/theme-css.d.ts +19 -0
  116. package/dist/utils/theme-css.js +100 -0
  117. package/oclif.manifest.json +3012 -1133
  118. package/package.json +28 -15
  119. package/schemas/global-config.schema.json +2 -6
  120. package/schemas/project-config.schema.json +37 -7
  121. package/dist/commands/seo/diff.js +0 -5
  122. package/dist/commands/seo/list.d.ts +0 -18
  123. package/dist/commands/seo/list.js +0 -58
  124. package/dist/commands/seo/pull.d.ts +0 -24
  125. package/dist/commands/seo/pull.js +0 -89
  126. package/dist/commands/seo/push.d.ts +0 -29
  127. package/dist/commands/seo/push.js +0 -178
  128. package/dist/commands/seo/rollback.js +0 -5
  129. package/dist/utils/seo-format.d.ts +0 -29
  130. package/dist/utils/seo-format.js +0 -18
  131. /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
  132. /package/dist/commands/{seo → cpt}/rollback.d.ts +0 -0
@@ -1,9 +1,8 @@
1
- import { select, Separator } from '@inquirer/prompts';
2
- import { Command, ux } from '@oclif/core';
1
+ import { select } from '@inquirer/prompts';
2
+ import { Command } from '@oclif/core';
3
3
  import { configManager } from '../../config/project-config.manager.js';
4
- const c = ux.colorize;
5
4
  export default class Switch extends Command {
6
- static description = 'Switch the active project and environment';
5
+ static description = 'Switch the active project (pass --env to target a non-local environment)';
7
6
  static examples = ['$ lps project switch'];
8
7
  async run() {
9
8
  await this.parse(Switch);
@@ -11,37 +10,18 @@ export default class Switch extends Command {
11
10
  if (projects.length === 0) {
12
11
  this.error('No projects configured. Run `lps project config` first.');
13
12
  }
14
- const { envName, projectId, projectName } = await this.resolveSelection(projects);
15
- configManager.setCurrent(projectId, envName);
16
- this.log(`✓ Switched to "${projectName}/${envName}"`);
17
- }
18
- async resolveSelection(projects) {
19
- const groups = projects
20
- .map((project) => ({ envs: configManager.listEnvironments(project.id), project }))
21
- .filter(({ envs }) => envs.length > 0);
22
- if (groups.length === 0) {
23
- this.error('No environments configured. Run `lps project config` first.');
24
- }
25
- const entries = groups.flatMap(({ envs, project }) => envs.map((env) => ({ env, project })));
26
- if (entries.length === 1) {
27
- const { env, project } = entries[0];
28
- return { envName: env.name, projectId: project.id, projectName: project.name };
29
- }
30
- const choices = groups.flatMap(({ envs, project }) => [
31
- new Separator(c(project.isCurrent ? 'green' : 'dim', `─── ${project.name} ───`)),
32
- ...envs.map((env) => ({
33
- name: `${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
34
- value: `${project.id}::${env.name}`,
35
- })),
36
- ]);
37
- const current = entries.find(({ env }) => env.isCurrent);
38
- const chosen = await select({
39
- choices,
40
- default: current && `${current.project.id}::${current.env.name}`,
41
- message: 'Select project / environment',
42
- });
43
- const [projectId, envName] = chosen.split('::', 2);
44
- const { project } = groups.find((group) => group.project.id === projectId);
45
- return { envName, projectId, projectName: project.name };
13
+ const projectId = projects.length === 1
14
+ ? projects[0].id
15
+ : await select({
16
+ choices: projects.map((project) => ({
17
+ name: `${project.name}${project.isCurrent ? ' [current]' : ''}`,
18
+ value: project.id,
19
+ })),
20
+ default: projects.find((project) => project.isCurrent)?.id,
21
+ message: 'Select project',
22
+ });
23
+ configManager.setCurrent(projectId);
24
+ const project = projects.find((candidate) => candidate.id === projectId);
25
+ this.log(`✓ Switched to "${project.name}"`);
46
26
  }
47
27
  }
@@ -7,17 +7,21 @@ import { pluralize } from '../utils/pluralize.js';
7
7
  const PULL_TARGETS = [
8
8
  { commandId: 'composer:pull', label: 'composer' },
9
9
  { commandId: 'plugin:pull', label: 'plugins' },
10
+ { commandId: 'cpt:pull', label: 'post types', supportsYes: true },
11
+ { commandId: 'taxonomy:pull', label: 'taxonomies', supportsYes: true },
12
+ { commandId: 'meta:pull', label: 'post meta', supportsYes: true },
13
+ { commandId: 'role:pull', label: 'roles', supportsYes: true },
10
14
  { commandId: 'acf:pull', label: 'ACF', supportsYes: true },
11
15
  { commandId: 'api:pull', label: 'API routes', supportsYes: true },
12
16
  { commandId: 'hook:pull', label: 'hooks', supportsYes: true },
13
17
  { commandId: 'form:pull', label: 'forms', supportsYes: true },
14
- { commandId: 'seo:pull', label: 'SEO', supportsYes: true },
18
+ { commandId: 'pattern:pull', label: 'patterns', supportsYes: true },
15
19
  { commandId: 'menu:pull', label: 'menus', supportsYes: true },
16
20
  { commandId: 'option:pull', label: 'options', supportsYes: true },
17
21
  { commandId: 'snippet:pull', label: 'snippets', supportsYes: true },
18
22
  ];
19
23
  export default class Pull extends LoopressCommand {
20
- static description = 'Pull all content, plugins, composer dependencies, ACF, API routes, hooks, forms, SEO, menus, options, and snippets, from WordPress';
24
+ static description = 'Pull all content, plugins, composer dependencies, post types, taxonomies, post meta, roles, ACF, API routes, hooks, forms, patterns, menus, options, and snippets, from WordPress';
21
25
  static enableJsonFlag = true;
22
26
  static examples = ['$ lps pull', '$ lps pull --env staging', '$ lps pull --dry-run'];
23
27
  static flags = {
@@ -27,6 +31,7 @@ export default class Pull extends LoopressCommand {
27
31
  failedCount = 0;
28
32
  async run() {
29
33
  const results = [];
34
+ this.warnRemovedSeoModule();
30
35
  for (const target of PULL_TARGETS) {
31
36
  this.log(`\n→ Pulling ${target.label}...`);
32
37
  try {
@@ -12,20 +12,26 @@ import { pluralize } from '../utils/pluralize.js';
12
12
  const PUSH_TARGETS = [
13
13
  { commandId: 'plugin:push', label: 'plugins' },
14
14
  { commandId: 'composer:push', label: 'composer' },
15
+ // Before ACF: a field group's location rule can target a Loopress post type or taxonomy.
16
+ { commandId: 'cpt:push', label: 'post types' },
17
+ { commandId: 'taxonomy:push', label: 'taxonomies' },
18
+ { commandId: 'meta:push', label: 'post meta' },
19
+ { commandId: 'role:push', label: 'roles' },
15
20
  { commandId: 'acf:push', label: 'ACF' },
16
21
  { commandId: 'api:push', label: 'API routes' },
17
22
  { commandId: 'hook:push', label: 'hooks' },
18
23
  { commandId: 'form:push', label: 'forms' },
24
+ // Before templates and pages, which can reference them (`wp:pattern`).
25
+ { commandId: 'pattern:push', label: 'patterns' },
19
26
  // Before pages: WordPress refuses a page whose `template` it doesn't know yet.
20
27
  { commandId: 'theme:template:push', label: 'templates and parts' },
21
28
  { commandId: 'page:push', label: 'pages' },
22
- { commandId: 'seo:push', label: 'SEO' },
23
29
  { commandId: 'menu:push', label: 'menus' },
24
30
  { commandId: 'option:push', label: 'options' },
25
31
  { commandId: 'snippet:push', label: 'snippets' },
26
32
  ];
27
33
  export default class Push extends LoopressCommand {
28
- static description = 'Push all local content, plugins, composer dependencies, ACF, API routes, hooks, forms, templates, pages, SEO, menus, options, and snippets, to WordPress';
34
+ static description = 'Push all local content, plugins, composer dependencies, post types, taxonomies, post meta, roles, ACF, API routes, hooks, forms, patterns, templates, pages, menus, options, and snippets, to WordPress';
29
35
  static enableJsonFlag = true;
30
36
  static examples = ['$ lps push', '$ lps push --env staging', '$ lps push --dry-run'];
31
37
  static flags = {
@@ -35,6 +41,7 @@ export default class Push extends LoopressCommand {
35
41
  failedCount = 0;
36
42
  async run() {
37
43
  await this.guardProductionPush();
44
+ this.warnRemovedSeoModule();
38
45
  const argv = this.buildArgv();
39
46
  const results = [];
40
47
  const composerPushedByPlugins = await this.composerDeclaresPlugins();
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/diff-command.js").DiffCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceDiffCommand } from '../../lib/diff-command.js';
2
+ export default resourceDiffCommand('role', {
3
+ description: 'Show what differs in roles between your local roles/ files and a WordPress environment, or between two environments',
4
+ pathNoun: 'roles directory',
5
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredListCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredListCommand } from '../../lib/declared-type-command.js';
2
+ import { ROLE_SPEC } from '../../utils/declared-type-format.js';
3
+ import { pluralize } from '../../utils/pluralize.js';
4
+ export default declaredListCommand(ROLE_SPEC, 'List the user roles on WordPress, where each comes from (Loopress, WordPress, a plugin) and how many users have it. Only Loopress ones are pulled and pushed.', (count) => pluralize(count, 'user'));
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPullCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPullCommand } from '../../lib/declared-type-command.js';
2
+ import { ROLE_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPullCommand(ROLE_SPEC, 'Pull the roles Loopress manages on WordPress into roles/<slug>.json files. ' +
4
+ 'Built-in roles and roles created by a plugin are never pulled, see `lps role list`.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPushCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPushCommand } from '../../lib/declared-type-command.js';
2
+ import { ROLE_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPushCommand(ROLE_SPEC, 'Push local roles (roles/<slug>.json, a "name" and its "capabilities") to WordPress. ' +
4
+ 'Create or update only, never deletes a role (use `lps role rm`), never changes which users have it.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredRmCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredRmCommand } from '../../lib/declared-type-command.js';
2
+ import { ROLE_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredRmCommand(ROLE_SPEC, 'Stop registering a Loopress role on WordPress. Its users keep it in their profile, with no capability until the role is pushed again.', 'Its users keep it in their profile, with no capability until it is pushed again.');
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/base.js").LoopressCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceRollbackCommand } from '../../lib/rollback-command.js';
2
+ export default resourceRollbackCommand('role', {
3
+ description: 'Restore roles to a snapshot taken automatically before an earlier `lps role push`',
4
+ pathNoun: 'roles directory',
5
+ });
@@ -18,8 +18,7 @@ export default class Status extends Command {
18
18
  env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  };
20
20
  run(): Promise<StatusResult>;
21
- private reportActiveProject;
22
- private reportEnvOverride;
23
- private reportPinnedProject;
21
+ private logTarget;
22
+ private report;
24
23
  }
25
24
  export {};
@@ -11,52 +11,26 @@ export default class Status extends Command {
11
11
  };
12
12
  async run() {
13
13
  const { flags } = await this.parse(Status);
14
- const localConfig = await readLocalConfig();
15
- let info;
16
- if (flags.env) {
17
- info = this.reportEnvOverride(localConfig.projectId, flags.env);
18
- }
19
- else if (localConfig.projectId) {
20
- info = this.reportPinnedProject(localConfig.projectId);
21
- }
22
- else {
23
- info = this.reportActiveProject();
24
- }
14
+ const { projectId } = await readLocalConfig();
15
+ const info = this.report(projectId, flags.env);
25
16
  this.log('');
26
17
  this.log(`Config dir: ${this.config.configDir}`);
27
18
  this.log(`Data dir: ${this.config.dataDir}`);
28
19
  return { ...info, configDir: this.config.configDir, dataDir: this.config.dataDir };
29
20
  }
30
- reportActiveProject() {
31
- const env = configManager.getCurrentEnv();
32
- const project = env ? configManager.getCurrentProject() : undefined;
33
- if (!env || !project) {
34
- const note = 'No project configured. Run `lps project config` first.';
35
- this.log(note);
36
- return { note };
37
- }
38
- this.log(`Project: ${project.name} (${env.name})`);
21
+ logTarget(label, env) {
22
+ this.log(`Project: ${label}`);
39
23
  this.log(`URL: ${env.url}`);
40
- return { environment: env.name, project: `${project.name} (${env.name})`, url: env.url };
24
+ return { environment: env.name, project: label, url: env.url };
41
25
  }
42
- // Mirrors base.ts:resolveEnvironment with --env: the targeted project comes from
43
- // loopress.json when pinned, from the globally active project otherwise.
44
- reportEnvOverride(pinnedProjectId, envName) {
45
- const project = pinnedProjectId ? configManager.getProject(pinnedProjectId) : configManager.getCurrentProject();
46
- if (!project) {
26
+ // Mirrors base.ts:resolveEnvironment, reporting instead of failing where it can.
27
+ report(pinnedProjectId, envName) {
28
+ const projectId = pinnedProjectId ?? configManager.getCurrentProject()?.id;
29
+ if (!projectId) {
47
30
  const note = 'No project configured. Run `lps project config` first.';
48
31
  this.log(note);
49
32
  return { note };
50
33
  }
51
- const env = project.environments[envName];
52
- if (!env) {
53
- this.error(`Environment "${envName}" not found in project "${project.name}". Available: ${Object.keys(project.environments).join(', ')}`);
54
- }
55
- this.log(`Project: ${project.name} (${env.name}, via --env)`);
56
- this.log(`URL: ${env.url}`);
57
- return { environment: env.name, project: `${project.name} (${env.name}, via --env)`, url: env.url };
58
- }
59
- reportPinnedProject(projectId) {
60
34
  const project = configManager.getProject(projectId);
61
35
  if (!project) {
62
36
  const note = `loopress.json pins project "${projectId}", but it no longer exists. Run \`lps project config\` to configure it.`;
@@ -71,28 +45,22 @@ export default class Status extends Command {
71
45
  this.log(note);
72
46
  return { note, project: project.name };
73
47
  }
74
- if (envNames.length === 1) {
75
- const env = project.environments[envNames[0]];
76
- this.log(`Project: ${project.name} (${env.name})`);
77
- this.log(`URL: ${env.url}`);
78
- return { environment: env.name, project: `${project.name} (${env.name})`, url: env.url };
48
+ if (envName) {
49
+ const env = project.environments[envName];
50
+ if (!env) {
51
+ this.error(`Environment "${envName}" not found in project "${project.name}". Available: ${envNames.join(', ')}`);
52
+ }
53
+ return this.logTarget(`${project.name} (${env.name}, via --env)`, env);
79
54
  }
80
- const current = configManager.getCurrentProject();
81
- const currentEnv = current?.id === projectId ? configManager.getCurrentEnv() : null;
82
- if (!currentEnv) {
83
- const note = `"${project.name}" has multiple environments and isn't the globally active project. Run \`lps project switch\` to pick one.`;
84
- this.log(`Project: ${project.name} ${c('yellow', '(ambiguous)')}`);
55
+ const env = configManager.getDefaultEnvironment(projectId);
56
+ if (!env) {
57
+ const note = `"${project.name}" has no "local" environment, pass --env to pick one.`;
58
+ this.log(`Project: ${project.name} ${c('yellow', '(no default environment)')}`);
85
59
  this.log(`Environments: ${envNames.join(', ')}`);
86
60
  this.log('');
87
- this.warn(`"${project.name}" has multiple environments and isn't the globally active project.`);
88
- this.log('Run `lps project switch` to pick one before running commands here.');
89
- if (current) {
90
- this.log(`(Globally active project right now: "${current.name}")`);
91
- }
61
+ this.warn(note);
92
62
  return { environments: envNames, note, project: project.name };
93
63
  }
94
- this.log(`Project: ${project.name} (${currentEnv.name})`);
95
- this.log(`URL: ${currentEnv.url}`);
96
- return { environment: currentEnv.name, project: `${project.name} (${currentEnv.name})`, url: currentEnv.url };
64
+ return this.logTarget(`${project.name} (${env.name})`, env);
97
65
  }
98
66
  }
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/diff-command.js").DiffCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceDiffCommand } from '../../lib/diff-command.js';
2
+ export default resourceDiffCommand('taxonomy', {
3
+ description: 'Show what differs in taxonomies between your local taxonomies/ files and a WordPress environment, or between two environments',
4
+ pathNoun: 'taxonomies directory',
5
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredListCommand>;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { declaredListCommand } from '../../lib/declared-type-command.js';
2
+ import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
3
+ import { pluralize } from '../../utils/pluralize.js';
4
+ export default declaredListCommand(TAXONOMY_SPEC, 'List the taxonomies registered on WordPress, where each comes from (Loopress, WordPress, ACF, CPT UI, a theme or plugin) ' +
5
+ 'and the post types it attaches to. Only Loopress ones are pulled and pushed.', (count) => pluralize(count, 'term'));
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPullCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPullCommand } from '../../lib/declared-type-command.js';
2
+ import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPullCommand(TAXONOMY_SPEC, 'Pull the taxonomies Loopress manages on WordPress into taxonomies/<slug>.json files, never their terms. ' +
4
+ 'Taxonomies registered by a theme or another plugin are never pulled, see `lps taxonomy list`.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPushCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPushCommand } from '../../lib/declared-type-command.js';
2
+ import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPushCommand(TAXONOMY_SPEC, 'Push local taxonomies (taxonomies/<slug>.json, register_taxonomy() arguments plus "object_type") to WordPress. ' +
4
+ 'Create or update only, never deletes a taxonomy (use `lps taxonomy rm`), never touches its terms.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredRmCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredRmCommand } from '../../lib/declared-type-command.js';
2
+ import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredRmCommand(TAXONOMY_SPEC, 'Stop registering a Loopress taxonomy on WordPress. Its terms are kept in the database, hidden until the taxonomy is pushed again.', "Its terms are kept in the database, hidden until it's pushed again.");
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/base.js").LoopressCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceRollbackCommand } from '../../lib/rollback-command.js';
2
+ export default resourceRollbackCommand('taxonomy', {
3
+ description: 'Restore taxonomies to a snapshot taken automatically before an earlier `lps taxonomy push`',
4
+ pathNoun: 'taxonomies directory',
5
+ });
@@ -1,11 +1,13 @@
1
1
  import { PushCommand } from '../../../lib/push-command.js';
2
2
  type PushResult = {
3
3
  active?: boolean;
4
+ blockStyles: string[];
4
5
  customized?: string[];
5
6
  parts: string[];
6
7
  status: 'dry-run' | 'success';
7
8
  stylesheet?: string;
8
9
  templates: string[];
10
+ unknownBlocks?: string[];
9
11
  };
10
12
  export default class Push extends PushCommand {
11
13
  static description: string;
@@ -4,8 +4,9 @@ import { formatPageProblems } from '../../../utils/page-format.js';
4
4
  import { pluralize } from '../../../utils/pluralize.js';
5
5
  import { resolveResourceDir } from '../../../utils/resource-dirs.js';
6
6
  import { CHILD_THEME_ENDPOINT, readLocalTemplates } from '../../../utils/template-format.js';
7
+ import { bundleThemeCss } from '../../../utils/theme-css.js';
7
8
  export default class Push extends PushCommand {
8
- static description = 'Push block templates (templates/<slug>.html) and template parts (parts/<slug>.html) as the files of a child theme of the active block theme, <parent>-loopress. The child mirrors the project: files removed locally are removed from it. It is never activated for you.';
9
+ static description = 'Push block templates (theme/templates/<slug>.html), template parts (theme/parts/<slug>.html) and the theme CSS (theme/css/style.css, theme/blocks/<namespace>/<block>.css, bundled with esbuild) as the files of a child theme of the active block theme, <parent>-loopress. The child mirrors the project: files removed locally are removed from it. It is never activated for you.';
9
10
  static enableJsonFlag = true;
10
11
  static examples = ['$ lps theme template push', '$ lps theme template push --dry-run'];
11
12
  static flags = {
@@ -19,17 +20,24 @@ export default class Push extends PushCommand {
19
20
  this.log(`Pushing templates and parts to ${this.siteConfig.url}`);
20
21
  this.log(`Templates path: ${templatesPath}, parts path: ${partsPath}`);
21
22
  const { parts, problems, templates } = await readLocalTemplates(templatesPath, partsPath);
23
+ const { css, problems: cssProblems } = await bundleThemeCss(resolveResourceDir('css', this.localConfig), resolveResourceDir('blockCss', this.localConfig));
24
+ problems.push(...cssProblems);
22
25
  if (problems.length > 0) {
23
26
  this.error(`Nothing was pushed, fix these files first:\n ${formatPageProblems(problems)}`);
24
27
  }
25
- const names = { parts: parts.map((part) => part.slug), templates: templates.map((template) => template.slug) };
26
- this.log(`Found ${pluralize(templates.length, 'template')} and ${pluralize(parts.length, 'part')}`);
28
+ const names = {
29
+ blockStyles: css.blocks.map((block) => block.name),
30
+ parts: parts.map((part) => part.slug),
31
+ templates: templates.map((template) => template.slug),
32
+ };
33
+ this.log(`Found ${pluralize(templates.length, 'template')}, ${pluralize(parts.length, 'part')}, ` +
34
+ `${css.style ? 'a global stylesheet' : 'no global stylesheet'} and ${pluralize(css.blocks.length, 'block stylesheet')}`);
27
35
  if (this.dryRun) {
28
36
  await this.previewRemote(getResourceStateProvider('template'), templatesPath);
29
37
  this.log('[dry-run] Would rewrite the child theme with them.');
30
38
  return { ...names, status: 'dry-run' };
31
39
  }
32
- const child = await this.wp.put(CHILD_THEME_ENDPOINT, { parts, templates });
40
+ const child = await this.wp.put(CHILD_THEME_ENDPOINT, { ...css, parts, templates });
33
41
  await this.recordSuccess();
34
42
  this.log(`Child theme ${child.stylesheet} written.`);
35
43
  if (!child.active) {
@@ -39,6 +47,19 @@ export default class Push extends PushCommand {
39
47
  this.warn(`Edited in the Site Editor, their database copy still wins over the pushed file: ${child.customized.join(', ')}. ` +
40
48
  'Clear their customizations in Appearance > Editor to use the pushed version.');
41
49
  }
42
- return { ...names, active: child.active, customized: child.customized, status: 'success', stylesheet: child.stylesheet };
50
+ // A warning, never a refusal: the block may come from a plugin active on another environment.
51
+ const unknownBlocks = child.unknownBlocks ?? [];
52
+ if (unknownBlocks.length > 0) {
53
+ this.warn(`No block registered on this site under these names, their CSS will never load: ${unknownBlocks.join(', ')}. ` +
54
+ 'Check the file name in theme/blocks/, or activate the plugin that provides the block.');
55
+ }
56
+ return {
57
+ ...names,
58
+ active: child.active,
59
+ customized: child.customized,
60
+ status: 'success',
61
+ stylesheet: child.stylesheet,
62
+ unknownBlocks,
63
+ };
43
64
  }
44
65
  }
@@ -8,15 +8,15 @@ export declare class ProjectConfigManager {
8
8
  id: string;
9
9
  });
10
10
  getConfigFilePath(): string;
11
- getCurrentEnv(): EnvironmentConfig | null;
12
11
  getCurrentProject(): null | (ProjectConfig & {
13
12
  id: string;
14
13
  });
14
+ getDefaultEnvironment(projectId: string): EnvironmentConfig | null;
15
15
  getEnvironment(projectId: string, envName: string): EnvironmentConfig | null;
16
16
  getProject(id: string): null | ProjectConfig;
17
17
  isTelemetryDisabled(): boolean;
18
18
  listEnvironments(projectId: string): Array<EnvironmentConfig & {
19
- isCurrent: boolean;
19
+ isDefault: boolean;
20
20
  }>;
21
21
  listProjects(): Array<ProjectConfig & {
22
22
  id: string;
@@ -26,7 +26,7 @@ export declare class ProjectConfigManager {
26
26
  removeEnvironment(projectId: string, envName: string): void;
27
27
  removeProject(id: string): void;
28
28
  setConfigDir(configDir: string): void;
29
- setCurrent(projectId: string, envName: string): void;
29
+ setCurrent(projectId: string): void;
30
30
  setEnvironment(projectId: string, envName: string, env: EnvironmentConfig): void;
31
31
  setEnvironmentApiId(projectId: string, envName: string, apiEnvironmentId: string): void;
32
32
  setProject(id: string, project: ProjectConfig): void;
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { LOCAL_ENVIRONMENT_NAME } from '../lib/wp-client.js';
3
4
  import { toSlug } from '../utils/to-slug.js';
4
5
  import { readJsonFile, writeJsonFileAtomic } from './json-file.js';
5
6
  export class ProjectConfigManager {
@@ -39,15 +40,6 @@ export class ProjectConfigManager {
39
40
  getConfigFilePath() {
40
41
  return join(this.requireConfigDir(), 'config.json');
41
42
  }
42
- getCurrentEnv() {
43
- const config = this.readConfig();
44
- if (!config.currentProject)
45
- return null;
46
- const project = config.projects[config.currentProject.id];
47
- if (!project)
48
- return null;
49
- return project.environments[config.currentProject.env] ?? null;
50
- }
51
43
  getCurrentProject() {
52
44
  const config = this.readConfig();
53
45
  if (!config.currentProject)
@@ -57,6 +49,17 @@ export class ProjectConfigManager {
57
49
  return null;
58
50
  return { ...project, id: config.currentProject.id };
59
51
  }
52
+ // The environment a command targets without --env: "local" when the project has one (where
53
+ // you work, so it's the safe default), otherwise its only environment. Null when the project
54
+ // has several remote ones and no local: the user must say which one with --env, since a
55
+ // remembered choice is exactly the hidden state that sends a push to the wrong site.
56
+ getDefaultEnvironment(projectId) {
57
+ const environments = this.getProject(projectId)?.environments ?? {};
58
+ const names = Object.keys(environments);
59
+ if (Object.hasOwn(environments, LOCAL_ENVIRONMENT_NAME))
60
+ return environments[LOCAL_ENVIRONMENT_NAME];
61
+ return names.length === 1 ? environments[names[0]] : null;
62
+ }
60
63
  getEnvironment(projectId, envName) {
61
64
  const project = this.getProject(projectId);
62
65
  if (!project)
@@ -71,14 +74,11 @@ export class ProjectConfigManager {
71
74
  return this.readConfig().telemetry?.disabled ?? false;
72
75
  }
73
76
  listEnvironments(projectId) {
74
- const config = this.readConfig();
75
- const project = config.projects[projectId];
77
+ const project = this.getProject(projectId);
76
78
  if (!project)
77
79
  return [];
78
- return Object.values(project.environments).map((env) => ({
79
- ...env,
80
- isCurrent: config.currentProject?.id === projectId && config.currentProject.env === env.name,
81
- }));
80
+ const defaultEnv = this.getDefaultEnvironment(projectId);
81
+ return Object.values(project.environments).map((env) => ({ ...env, isDefault: env.name === defaultEnv?.name }));
82
82
  }
83
83
  listProjects() {
84
84
  const config = this.readConfig();
@@ -101,10 +101,6 @@ export class ProjectConfigManager {
101
101
  if (!project)
102
102
  return;
103
103
  Reflect.deleteProperty(project.environments, envName);
104
- if (config.currentProject?.id === projectId && config.currentProject.env === envName) {
105
- const remaining = Object.keys(project.environments);
106
- config.currentProject = remaining.length > 0 ? { env: remaining[0], id: projectId } : null;
107
- }
108
104
  this.writeConfig(config);
109
105
  }
110
106
  removeProject(id) {
@@ -112,9 +108,7 @@ export class ProjectConfigManager {
112
108
  Reflect.deleteProperty(config.projects, id);
113
109
  if (config.currentProject?.id === id) {
114
110
  const [nextId] = Object.keys(config.projects);
115
- const nextProject = nextId ? config.projects[nextId] : undefined;
116
- const [nextEnv] = nextProject ? Object.keys(nextProject.environments) : [];
117
- config.currentProject = nextId && nextEnv ? { env: nextEnv, id: nextId } : null;
111
+ config.currentProject = nextId ? { id: nextId } : null;
118
112
  }
119
113
  this.writeConfig(config);
120
114
  }
@@ -124,11 +118,11 @@ export class ProjectConfigManager {
124
118
  setConfigDir(configDir) {
125
119
  this.configDir = configDir;
126
120
  }
127
- setCurrent(projectId, envName) {
121
+ setCurrent(projectId) {
128
122
  const config = this.readConfig();
129
123
  if (!Object.hasOwn(config.projects, projectId))
130
124
  return;
131
- config.currentProject = { env: envName, id: projectId };
125
+ config.currentProject = { id: projectId };
132
126
  this.writeConfig(config);
133
127
  }
134
128
  setEnvironment(projectId, envName, env) {
@@ -137,7 +131,7 @@ export class ProjectConfigManager {
137
131
  if (!project)
138
132
  return;
139
133
  project.environments[envName] = env;
140
- config.currentProject ??= { env: envName, id: projectId };
134
+ config.currentProject ??= { id: projectId };
141
135
  this.writeConfig(config);
142
136
  }
143
137
  setEnvironmentApiId(projectId, envName, apiEnvironmentId) {
@@ -151,11 +145,7 @@ export class ProjectConfigManager {
151
145
  setProject(id, project) {
152
146
  const config = this.readConfig();
153
147
  config.projects[id] = project;
154
- if (!config.currentProject) {
155
- const [firstEnv] = Object.keys(project.environments);
156
- if (firstEnv)
157
- config.currentProject = { env: firstEnv, id };
158
- }
148
+ config.currentProject ??= { id };
159
149
  this.writeConfig(config);
160
150
  }
161
151
  setProjectApiId(id, apiProjectId) {
@@ -203,7 +193,8 @@ export class ProjectConfigManager {
203
193
  if (value === null || typeof value !== 'object')
204
194
  return null;
205
195
  const pointer = value;
206
- return typeof pointer.id === 'string' && typeof pointer.env === 'string' ? { env: pointer.env, id: pointer.id } : null;
196
+ // A legacy `env` field, from when switch also picked the environment, is dropped here.
197
+ return typeof pointer.id === 'string' ? { id: pointer.id } : null;
207
198
  }
208
199
  sanitizeProjects(value) {
209
200
  if (typeof value !== 'object' || value === null)