@loopress/cli 0.3.0 → 0.5.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 (33) hide show
  1. package/README.md +88 -334
  2. package/dist/commands/init.d.ts +6 -0
  3. package/dist/commands/init.js +73 -0
  4. package/dist/commands/{styles → plugin}/pull.d.ts +1 -1
  5. package/dist/commands/plugin/pull.js +48 -0
  6. package/dist/commands/{styles → plugin}/push.d.ts +4 -4
  7. package/dist/commands/plugin/push.js +113 -0
  8. package/dist/commands/plugin/require.d.ts +17 -0
  9. package/dist/commands/plugin/require.js +74 -0
  10. package/dist/commands/project/config.js +2 -2
  11. package/dist/commands/{snippets → snippet}/list.d.ts +1 -1
  12. package/dist/commands/{snippets → snippet}/list.js +3 -3
  13. package/dist/commands/{snippets → snippet}/pull.d.ts +4 -1
  14. package/dist/commands/{snippets → snippet}/pull.js +26 -37
  15. package/dist/commands/{snippets → snippet}/push.d.ts +4 -5
  16. package/dist/commands/{snippets → snippet}/push.js +50 -39
  17. package/dist/config/auth.manager.js +2 -2
  18. package/dist/config/project-config.manager.js +2 -2
  19. package/dist/lib/base.d.ts +1 -1
  20. package/dist/lib/base.js +18 -6
  21. package/dist/lib/push-command.d.ts +7 -0
  22. package/dist/lib/push-command.js +32 -0
  23. package/dist/types/plugin.d.ts +15 -0
  24. package/dist/types/plugin.js +1 -0
  25. package/dist/utils/loopress-config.d.ts +5 -2
  26. package/dist/utils/loopress-config.js +8 -3
  27. package/dist/utils/plugins.d.ts +27 -0
  28. package/dist/utils/plugins.js +34 -0
  29. package/dist/utils/snippet-plugin.js +1 -1
  30. package/oclif.manifest.json +217 -131
  31. package/package.json +14 -19
  32. package/dist/commands/styles/pull.js +0 -52
  33. package/dist/commands/styles/push.js +0 -78
@@ -0,0 +1,73 @@
1
+ import { confirm, input, select } from '@inquirer/prompts';
2
+ import { Command } from '@oclif/core';
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { configManager } from '../config/project-config.manager.js';
6
+ import { writeLocalConfig } from '../utils/loopress-config.js';
7
+ export default class Init extends Command {
8
+ static description = 'Initialize a loopress.json config file in the current directory';
9
+ static examples = ['$ lps init'];
10
+ async run() {
11
+ await this.parse(Init);
12
+ const configPath = join(process.cwd(), 'loopress.json');
13
+ if (existsSync(configPath)) {
14
+ const overwrite = await confirm({
15
+ default: false,
16
+ message: 'loopress.json already exists. Overwrite?',
17
+ });
18
+ if (!overwrite) {
19
+ this.log('Aborted.');
20
+ return;
21
+ }
22
+ }
23
+ const projects = configManager.listProjects();
24
+ let projectId;
25
+ if (projects.length > 0) {
26
+ const choices = [
27
+ ...projects.map((p) => ({ name: p.name, value: p.name })),
28
+ { name: 'Enter a project ID manually', value: '__manual__' },
29
+ ];
30
+ const choice = await select({
31
+ choices,
32
+ message: 'WordPress project',
33
+ });
34
+ projectId = choice === '__manual__' ? (await input({
35
+ message: 'Project ID',
36
+ validate: (value) => (value.trim().length > 0 ? true : 'Project ID cannot be empty'),
37
+ })) : choice;
38
+ }
39
+ else {
40
+ this.log('No projects configured yet. Run `lps project config` to add one first.');
41
+ projectId = await input({
42
+ message: 'Project ID',
43
+ validate: (value) => (value.trim().length > 0 ? true : 'Project ID cannot be empty'),
44
+ });
45
+ }
46
+ const snippetPlugin = await select({
47
+ choices: [
48
+ { name: 'WPCode', value: 'wpcode' },
49
+ { name: 'Code Snippets', value: 'code-snippets' },
50
+ ],
51
+ message: 'Snippet plugin',
52
+ });
53
+ const rootDir = await input({
54
+ default: '.',
55
+ message: 'Root directory',
56
+ });
57
+ const snippetsDir = await input({
58
+ default: 'snippets',
59
+ message: 'Snippets directory (relative to root)',
60
+ });
61
+ const config = {
62
+ projectId,
63
+ rootDir,
64
+ snippetPlugin: snippetPlugin,
65
+ snippetsDir,
66
+ };
67
+ await writeLocalConfig(config);
68
+ this.log(`\n✓ loopress.json created`);
69
+ this.log(` Project: ${projectId}`);
70
+ this.log(` Plugin: ${snippetPlugin}`);
71
+ this.log(` Snippets: ${join(rootDir, snippetsDir)}`);
72
+ }
73
+ }
@@ -3,7 +3,7 @@ export default class Pull extends LoopressCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -0,0 +1,48 @@
1
+ import { Flags } from '@oclif/core';
2
+ import got from 'got';
3
+ import { LoopressCommand } from '../../lib/base.js';
4
+ import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
5
+ import { mergePluginManifest } from '../../utils/plugins.js';
6
+ export default class Pull extends LoopressCommand {
7
+ static description = 'Pull installed plugins from WordPress into loopress.json';
8
+ static examples = ['$ lps plugins pull', '$ lps plugins pull --dry-run'];
9
+ static flags = {
10
+ ...LoopressCommand.baseFlags,
11
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be written without making changes' }),
12
+ };
13
+ async run() {
14
+ const { flags } = await this.parse(Pull);
15
+ const dryRun = flags['dry-run'];
16
+ const { url } = this.siteConfig;
17
+ this.log(`Pulling plugins from ${url}`);
18
+ const headers = await this.buildAuthHeaders();
19
+ const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
20
+ const incoming = Object.fromEntries(installed.map((p) => [p.slug, p.version]));
21
+ const localConfig = await readLocalConfig();
22
+ const { added, merged, updated } = mergePluginManifest(localConfig.plugins ?? {}, incoming);
23
+ if (dryRun) {
24
+ this.log(`[dry-run] Would write ${Object.keys(merged).length} plugins to loopress.json`);
25
+ if (added.length > 0)
26
+ this.log(` + ${added.join(', ')}`);
27
+ if (updated.length > 0) {
28
+ for (const u of updated)
29
+ this.log(` ~ ${u.slug} (${u.from} → ${u.to})`);
30
+ }
31
+ return;
32
+ }
33
+ await writeLocalConfig({ ...localConfig, plugins: merged });
34
+ this.log(`Wrote ${Object.keys(merged).length} plugins to loopress.json`);
35
+ if (added.length > 0)
36
+ this.log(` + Added: ${added.join(', ')}`);
37
+ for (const u of updated)
38
+ this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
39
+ if (Object.keys(merged).length > 0) {
40
+ await got
41
+ .post(`${url}/wp-json/loopress/v1/plugins/auto-updates/disable`, {
42
+ headers,
43
+ json: { slugs: Object.keys(merged) },
44
+ })
45
+ .json();
46
+ }
47
+ }
48
+ }
@@ -1,13 +1,13 @@
1
- import { LoopressCommand } from '../../lib/base.js';
2
- export default class Push extends LoopressCommand {
1
+ import { PushCommand } from '../../lib/push-command.js';
2
+ export default class Push extends PushCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  };
11
11
  run(): Promise<void>;
12
- private readOrFetchGlobalStyles;
12
+ private activatePlugin;
13
13
  }
@@ -0,0 +1,113 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { Flags } from '@oclif/core';
3
+ import got from 'got';
4
+ import { PushCommand } from '../../lib/push-command.js';
5
+ import { readLocalConfig } from '../../utils/loopress-config.js';
6
+ import { diffPlugins } from '../../utils/plugins.js';
7
+ export default class Push extends PushCommand {
8
+ static description = 'Sync plugins on WordPress to match loopress.json';
9
+ static examples = ['$ lps plugins push', '$ lps plugins push --dry-run'];
10
+ static flags = {
11
+ ...PushCommand.baseFlags,
12
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would change without making changes' }),
13
+ };
14
+ async run() {
15
+ const { flags } = await this.parse(Push);
16
+ const dryRun = flags['dry-run'];
17
+ this.dryRun = dryRun;
18
+ const { url } = this.siteConfig;
19
+ const localConfig = await readLocalConfig();
20
+ const manifest = localConfig.plugins;
21
+ if (!manifest || Object.keys(manifest).length === 0) {
22
+ this.error('No plugins found in loopress.json. Run `lps plugins pull` first.');
23
+ }
24
+ this.log(`Pushing plugins to ${url}`);
25
+ const headers = await this.buildAuthHeaders();
26
+ const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
27
+ const { drifted, toActivate, toInstall } = diffPlugins(manifest, installed);
28
+ if (toInstall.length === 0 && toActivate.length === 0 && drifted.length === 0) {
29
+ this.log('Everything is already in sync.');
30
+ return;
31
+ }
32
+ if (toInstall.length > 0) {
33
+ this.log(`\nTo install (${toInstall.length}):`);
34
+ for (const a of toInstall)
35
+ this.log(` + ${a.slug} @ ${a.targetVersion}`);
36
+ }
37
+ if (toActivate.length > 0) {
38
+ this.log(`\nTo activate (${toActivate.length}):`);
39
+ for (const a of toActivate)
40
+ this.log(` ↑ ${a.slug}`);
41
+ }
42
+ if (drifted.length > 0) {
43
+ this.log(`\nVersion mismatch (${drifted.length}):`);
44
+ for (const a of drifted) {
45
+ this.log(` ~ ${a.slug}: site has ${a.currentVersion}, manifest wants ${a.targetVersion}`);
46
+ }
47
+ }
48
+ if (dryRun)
49
+ return;
50
+ // Install missing plugins and activate them.
51
+ for (const action of toInstall) {
52
+ this.log(`\nInstalling ${action.slug} @ ${action.targetVersion}...`);
53
+ try {
54
+ const result = await got
55
+ .post(`${url}/wp-json/loopress/v1/plugins/install`, {
56
+ headers,
57
+ json: { slug: action.slug, version: action.targetVersion },
58
+ })
59
+ .json();
60
+ this.log(` ✓ ${result.message}`);
61
+ }
62
+ catch (error) {
63
+ this.warn(` Failed to install ${action.slug}: ${error.message}`);
64
+ continue;
65
+ }
66
+ await this.activatePlugin(url, headers, action.slug);
67
+ }
68
+ // Activate installed-but-inactive plugins without prompting.
69
+ for (const action of toActivate) {
70
+ this.log(`\nActivating ${action.slug}...`);
71
+ await this.activatePlugin(url, headers, action.slug);
72
+ }
73
+ // Prompt per drifted plugin before syncing.
74
+ for (const action of drifted) {
75
+ this.log('');
76
+ const proceed = await confirm({
77
+ default: false,
78
+ message: `${action.slug} is at ${action.currentVersion} on the site but manifest wants ${action.targetVersion}. Sync to ${action.targetVersion}?`,
79
+ });
80
+ if (!proceed) {
81
+ this.log(` Skipped ${action.slug}`);
82
+ continue;
83
+ }
84
+ this.log(` Syncing ${action.slug} to ${action.targetVersion}...`);
85
+ try {
86
+ const result = await got
87
+ .post(`${url}/wp-json/loopress/v1/plugins/install`, {
88
+ headers,
89
+ json: { slug: action.slug, version: action.targetVersion },
90
+ })
91
+ .json();
92
+ this.log(` ✓ ${result.message}`);
93
+ }
94
+ catch (error) {
95
+ this.warn(` Failed to sync ${action.slug}: ${error.message}`);
96
+ continue;
97
+ }
98
+ await this.activatePlugin(url, headers, action.slug);
99
+ }
100
+ await this.recordSuccess();
101
+ }
102
+ async activatePlugin(url, headers, slug) {
103
+ try {
104
+ const result = await got
105
+ .post(`${url}/wp-json/loopress/v1/plugins/activate`, { headers, json: { slug } })
106
+ .json();
107
+ this.log(` ✓ ${result.message}`);
108
+ }
109
+ catch (error) {
110
+ this.warn(` Failed to activate ${slug}: ${error.message}`);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,17 @@
1
+ import { LoopressCommand } from '../../lib/base.js';
2
+ export declare function resolvePluginVersion(slug: string, version: string): Promise<string>;
3
+ export default class Require extends LoopressCommand {
4
+ static args: {
5
+ slug: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ version: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static description: string;
9
+ static examples: string[];
10
+ static flags: {
11
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,74 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import got from 'got';
3
+ import { LoopressCommand } from '../../lib/base.js';
4
+ import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
5
+ const WP_ORG_API = 'https://api.wordpress.org/plugins/info/1.2/';
6
+ export async function resolvePluginVersion(slug, version) {
7
+ if (version !== 'latest')
8
+ return version;
9
+ let info;
10
+ try {
11
+ info = await got
12
+ .get(WP_ORG_API, {
13
+ searchParams: {
14
+ action: 'plugin_information',
15
+ 'request[slug]': slug,
16
+ },
17
+ })
18
+ .json();
19
+ }
20
+ catch {
21
+ throw new Error(`Plugin "${slug}" not found on WordPress.org.`);
22
+ }
23
+ if (info.error)
24
+ throw new Error(`Plugin "${slug}" not found on WordPress.org.`);
25
+ return info.version;
26
+ }
27
+ export default class Require extends LoopressCommand {
28
+ static args = {
29
+ slug: Args.string({ description: 'Plugin slug (WordPress.org)', required: true }),
30
+ version: Args.string({ description: 'Version to pin (default: latest)' }),
31
+ };
32
+ static description = 'Add a plugin to loopress.json, resolving its latest version from WordPress.org';
33
+ static examples = [
34
+ '$ lps plugins require woocommerce',
35
+ '$ lps plugins require woocommerce 8.9.1',
36
+ '$ lps plugins require contact-form-7 --dry-run',
37
+ ];
38
+ static flags = {
39
+ ...LoopressCommand.baseFlags,
40
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be written without making changes' }),
41
+ };
42
+ async run() {
43
+ const { args, flags } = await this.parse(Require);
44
+ const dryRun = flags['dry-run'];
45
+ const { slug } = args;
46
+ const requestedVersion = args.version ?? 'latest';
47
+ this.log(`Resolving ${slug}@${requestedVersion}...`);
48
+ let resolvedVersion;
49
+ try {
50
+ resolvedVersion = await resolvePluginVersion(slug, requestedVersion);
51
+ }
52
+ catch (error) {
53
+ this.error(error.message);
54
+ }
55
+ this.log(`Resolved: ${slug}@${resolvedVersion}`);
56
+ const localConfig = await readLocalConfig();
57
+ const existing = localConfig.plugins ?? {};
58
+ if (existing[slug] === resolvedVersion) {
59
+ this.log(`${slug}@${resolvedVersion} is already in loopress.json — nothing to do.`);
60
+ return;
61
+ }
62
+ const updated = existing[slug] !== undefined;
63
+ const label = updated ? `${existing[slug]} → ${resolvedVersion}` : resolvedVersion;
64
+ if (dryRun) {
65
+ this.log(`[dry-run] Would ${updated ? 'update' : 'add'} ${slug}: ${label}`);
66
+ return;
67
+ }
68
+ await writeLocalConfig({
69
+ ...localConfig,
70
+ plugins: { ...existing, [slug]: resolvedVersion },
71
+ });
72
+ this.log(`${updated ? 'Updated' : 'Added'} ${slug}: ${label}`);
73
+ }
74
+ }
@@ -17,9 +17,9 @@ export default class Config extends Command {
17
17
  });
18
18
  const envChoice = await select({
19
19
  choices: [
20
- { name: 'production', value: 'production' },
20
+ { name: 'local', value: 'local' },
21
21
  { name: 'staging', value: 'staging' },
22
- { name: 'development', value: 'development' },
22
+ { name: 'production', value: 'production' },
23
23
  { name: 'Custom…', value: '__custom__' },
24
24
  ],
25
25
  message: 'Environment',
@@ -4,7 +4,7 @@ export default class List extends LoopressCommand {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
- plugin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -14,8 +14,7 @@ export default class List extends LoopressCommand {
14
14
  json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
15
15
  plugin: Flags.string({
16
16
  char: 'p',
17
- default: 'code-snippets',
18
- description: 'WordPress snippet plugin to target',
17
+ description: 'WordPress snippet plugin to target (overrides loopress.json)',
19
18
  options: ['code-snippets', 'wpcode'],
20
19
  }),
21
20
  };
@@ -23,8 +22,9 @@ export default class List extends LoopressCommand {
23
22
  const { flags } = await this.parse(List);
24
23
  const { json, plugin } = flags;
25
24
  const { url } = this.siteConfig;
25
+ const resolvedPlugin = await this.resolveSnippetPlugin(plugin);
26
26
  try {
27
- const adapter = getSnippetPlugin(plugin);
27
+ const adapter = getSnippetPlugin(resolvedPlugin);
28
28
  const endpoint = adapter.endpoint(url);
29
29
  const headers = await this.buildAuthHeaders();
30
30
  const remoteList = await got.get(endpoint, { headers }).json();
@@ -1,4 +1,7 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
+ import { NormalizedSnippet } from '../../utils/snippet-plugin.js';
3
+ export declare function buildSnippetFile(snippet: NormalizedSnippet): string;
4
+ export declare function buildMetaFile(snippet: NormalizedSnippet): string;
2
5
  export default class Pull extends LoopressCommand {
3
6
  static args: {
4
7
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -7,7 +10,7 @@ export default class Pull extends LoopressCommand {
7
10
  static examples: string[];
8
11
  static flags: {
9
12
  dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- plugin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
14
  password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
15
  url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
16
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -10,39 +10,24 @@ const EXTENSIONS = {
10
10
  php: 'php',
11
11
  text: 'txt',
12
12
  };
13
- const sanitize = (value) => value.replaceAll(/\s*\n\s*/g, ' ').trim();
14
- function buildMetaLines(snippet) {
15
- return [
16
- `id: ${snippet.id}`,
17
- `name: ${sanitize(snippet.name)}`,
18
- ...(snippet.description ? [`description: ${sanitize(snippet.description)}`] : []),
19
- `type: ${snippet.type}`,
20
- ...(snippet.tags.length > 0 ? [`tags: ${snippet.tags.map((t) => sanitize(t)).join(', ')}`] : []),
21
- `active: ${snippet.active}`,
22
- ];
23
- }
24
- function buildSnippetFile(snippet) {
25
- const meta = buildMetaLines(snippet);
26
- switch (snippet.type) {
27
- case 'css':
28
- case 'js': {
29
- const header = ['/**', ...meta.map((l) => ` * ${l}`), ' */'].join('\n');
30
- return `${header}\n\n${snippet.code}`;
31
- }
32
- case 'html': {
33
- const header = ['<!--', ...meta.map((l) => ` ${l}`), '-->'].join('\n');
34
- return `${header}\n\n${snippet.code}`;
35
- }
36
- case 'text': {
37
- return snippet.code;
38
- }
39
- case 'php':
40
- default: {
41
- const header = ['<?php', '/**', ...meta.map((l) => ` * ${l}`), ' */'].join('\n');
42
- const body = snippet.code.replace(/^<\?php\s*/i, '');
43
- return `${header}\n\n${body}`;
44
- }
13
+ export function buildSnippetFile(snippet) {
14
+ if (snippet.type === 'php' && !snippet.code.trimStart().startsWith('<?')) {
15
+ return `<?php\n\n${snippet.code}`;
45
16
  }
17
+ return snippet.code;
18
+ }
19
+ export function buildMetaFile(snippet) {
20
+ const meta = {
21
+ id: snippet.id,
22
+ name: snippet.name,
23
+ type: snippet.type,
24
+ active: snippet.active,
25
+ };
26
+ if (snippet.description)
27
+ meta.description = snippet.description;
28
+ if (snippet.tags.length > 0)
29
+ meta.tags = snippet.tags;
30
+ return JSON.stringify(meta, null, 2) + '\n';
46
31
  }
47
32
  export default class Pull extends LoopressCommand {
48
33
  static args = {
@@ -60,8 +45,7 @@ export default class Pull extends LoopressCommand {
60
45
  dryRun: Flags.boolean({ char: 'd', description: 'Dry run - show what would happen without making changes' }),
61
46
  plugin: Flags.string({
62
47
  char: 'p',
63
- default: 'code-snippets',
64
- description: 'WordPress snippet plugin to target',
48
+ description: 'WordPress snippet plugin to target (overrides loopress.json)',
65
49
  options: ['code-snippets', 'wpcode'],
66
50
  }),
67
51
  };
@@ -70,11 +54,12 @@ export default class Pull extends LoopressCommand {
70
54
  const { dryRun, plugin } = flags;
71
55
  const { url } = this.siteConfig;
72
56
  const path = await this.resolveSnippetsPath(args.path);
73
- this.log(`📥 Pulling snippets from ${url} via ${plugin}`);
57
+ const resolvedPlugin = await this.resolveSnippetPlugin(plugin);
58
+ this.log(`📥 Pulling snippets from ${url} via ${resolvedPlugin}`);
74
59
  this.log(`📂 From snippet path: ${path}`);
75
60
  this.log(`🔄 Dry run: ${dryRun ? 'yes' : 'no'}`);
76
61
  try {
77
- const adapter = getSnippetPlugin(plugin);
62
+ const adapter = getSnippetPlugin(resolvedPlugin);
78
63
  const endpoint = adapter.endpoint(url);
79
64
  const headers = await this.buildAuthHeaders();
80
65
  const remoteList = await got.get(endpoint, { headers }).json();
@@ -94,8 +79,12 @@ export default class Pull extends LoopressCommand {
94
79
  continue;
95
80
  }
96
81
  const ext = EXTENSIONS[snippet.type];
97
- const filePath = `${path}/${slugify(snippet.name, { lower: true, strict: true })}.${ext}`;
82
+ const slug = slugify(snippet.name, { lower: true, strict: true });
83
+ const base = `${snippet.id}-${slug}`;
84
+ const filePath = `${path}/${base}.${ext}`;
85
+ const metaPath = `${path}/${base}.json`;
98
86
  await fs.writeFile(filePath, buildSnippetFile(snippet));
87
+ await fs.writeFile(metaPath, buildMetaFile(snippet));
99
88
  count++;
100
89
  this.log(`✅ Pulled: ${snippet.name}`);
101
90
  }
@@ -1,5 +1,5 @@
1
- import { LoopressCommand } from '../../lib/base.js';
2
- export default class Push extends LoopressCommand {
1
+ import { PushCommand } from '../../lib/push-command.js';
2
+ export default class Push extends PushCommand {
3
3
  static args: {
4
4
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
5
  };
@@ -7,14 +7,13 @@ export default class Push extends LoopressCommand {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- plugin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
13
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
14
  };
15
15
  run(): Promise<void>;
16
- private injectIdIntoFile;
16
+ private injectIdIntoMeta;
17
17
  private loadSnippets;
18
- private parseMetaFromContent;
19
18
  private pushSnippet;
20
19
  }