@loopress/cli 0.4.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @loopress/cli
20
20
  $ lps COMMAND
21
21
  running command...
22
22
  $ lps (--version)
23
- @loopress/cli/0.4.0 linux-x64 node-v24.16.0
23
+ @loopress/cli/0.5.0 linux-x64 node-v24.17.0
24
24
  $ lps --help [COMMAND]
25
25
  USAGE
26
26
  $ lps COMMAND
@@ -32,6 +32,7 @@ USAGE
32
32
 
33
33
  <!-- commands -->
34
34
  * [`lps help [COMMAND]`](#lps-help-command)
35
+ * [`lps init`](#lps-init)
35
36
  * [`lps login`](#lps-login)
36
37
  * [`lps logout`](#lps-logout)
37
38
  * [`lps plugin pull`](#lps-plugin-pull)
@@ -46,8 +47,6 @@ USAGE
46
47
  * [`lps snippet list`](#lps-snippet-list)
47
48
  * [`lps snippet pull [PATH]`](#lps-snippet-pull-path)
48
49
  * [`lps snippet push [PATH]`](#lps-snippet-push-path)
49
- * [`lps style pull`](#lps-style-pull)
50
- * [`lps style push`](#lps-style-push)
51
50
 
52
51
  ## `lps help [COMMAND]`
53
52
 
@@ -67,7 +66,24 @@ DESCRIPTION
67
66
  Display help for lps.
68
67
  ```
69
68
 
70
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.50/src/commands/help.ts)_
69
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.52/src/commands/help.ts)_
70
+
71
+ ## `lps init`
72
+
73
+ Initialize a loopress.json config file in the current directory
74
+
75
+ ```
76
+ USAGE
77
+ $ lps init
78
+
79
+ DESCRIPTION
80
+ Initialize a loopress.json config file in the current directory
81
+
82
+ EXAMPLES
83
+ $ lps init
84
+ ```
85
+
86
+ _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/init.ts)_
71
87
 
72
88
  ## `lps login`
73
89
 
@@ -84,7 +100,7 @@ EXAMPLES
84
100
  $ lps login
85
101
  ```
86
102
 
87
- _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/login.ts)_
103
+ _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/login.ts)_
88
104
 
89
105
  ## `lps logout`
90
106
 
@@ -101,7 +117,7 @@ EXAMPLES
101
117
  $ lps logout
102
118
  ```
103
119
 
104
- _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/logout.ts)_
120
+ _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/logout.ts)_
105
121
 
106
122
  ## `lps plugin pull`
107
123
 
@@ -128,7 +144,7 @@ EXAMPLES
128
144
  $ lps plugins pull --dry-run
129
145
  ```
130
146
 
131
- _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/plugin/pull.ts)_
147
+ _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/pull.ts)_
132
148
 
133
149
  ## `lps plugin push`
134
150
 
@@ -155,7 +171,7 @@ EXAMPLES
155
171
  $ lps plugins push --dry-run
156
172
  ```
157
173
 
158
- _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/plugin/push.ts)_
174
+ _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/push.ts)_
159
175
 
160
176
  ## `lps plugin require SLUG [VERSION]`
161
177
 
@@ -188,7 +204,7 @@ EXAMPLES
188
204
  $ lps plugins require contact-form-7 --dry-run
189
205
  ```
190
206
 
191
- _See code: [src/commands/plugin/require.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/plugin/require.ts)_
207
+ _See code: [src/commands/plugin/require.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/require.ts)_
192
208
 
193
209
  ## `lps project config`
194
210
 
@@ -205,7 +221,7 @@ EXAMPLES
205
221
  $ lps project config
206
222
  ```
207
223
 
208
- _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/config.ts)_
224
+ _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/config.ts)_
209
225
 
210
226
  ## `lps project list`
211
227
 
@@ -222,7 +238,7 @@ EXAMPLES
222
238
  $ lps project list
223
239
  ```
224
240
 
225
- _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/list.ts)_
241
+ _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/list.ts)_
226
242
 
227
243
  ## `lps project remove`
228
244
 
@@ -239,7 +255,7 @@ EXAMPLES
239
255
  $ lps project remove
240
256
  ```
241
257
 
242
- _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/remove.ts)_
258
+ _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/remove.ts)_
243
259
 
244
260
  ## `lps project remove-env`
245
261
 
@@ -256,7 +272,7 @@ EXAMPLES
256
272
  $ lps project remove-env
257
273
  ```
258
274
 
259
- _See code: [src/commands/project/remove-env.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/remove-env.ts)_
275
+ _See code: [src/commands/project/remove-env.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/remove-env.ts)_
260
276
 
261
277
  ## `lps project switch`
262
278
 
@@ -273,7 +289,7 @@ EXAMPLES
273
289
  $ lps project switch
274
290
  ```
275
291
 
276
- _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/switch.ts)_
292
+ _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/switch.ts)_
277
293
 
278
294
  ## `lps project switch-env`
279
295
 
@@ -290,7 +306,7 @@ EXAMPLES
290
306
  $ lps project switch-env
291
307
  ```
292
308
 
293
- _See code: [src/commands/project/switch-env.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/project/switch-env.ts)_
309
+ _See code: [src/commands/project/switch-env.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/switch-env.ts)_
294
310
 
295
311
  ## `lps snippet list`
296
312
 
@@ -302,7 +318,7 @@ USAGE
302
318
 
303
319
  FLAGS
304
320
  -j, --json Output in JSON format
305
- -p, --plugin=<option> [default: code-snippets] WordPress snippet plugin to target
321
+ -p, --plugin=<option> WordPress snippet plugin to target (overrides loopress.json)
306
322
  <options: code-snippets|wpcode>
307
323
 
308
324
  GLOBAL FLAGS
@@ -321,7 +337,7 @@ EXAMPLES
321
337
  $ lps snippets list --plugin wpcode
322
338
  ```
323
339
 
324
- _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/snippet/list.ts)_
340
+ _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/list.ts)_
325
341
 
326
342
  ## `lps snippet pull [PATH]`
327
343
 
@@ -336,7 +352,7 @@ ARGUMENTS
336
352
 
337
353
  FLAGS
338
354
  -d, --dryRun Dry run - show what would happen without making changes
339
- -p, --plugin=<option> [default: code-snippets] WordPress snippet plugin to target
355
+ -p, --plugin=<option> WordPress snippet plugin to target (overrides loopress.json)
340
356
  <options: code-snippets|wpcode>
341
357
 
342
358
  GLOBAL FLAGS
@@ -357,7 +373,7 @@ EXAMPLES
357
373
  $ lps snippets pull --plugin wpcode
358
374
  ```
359
375
 
360
- _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/snippet/pull.ts)_
376
+ _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/pull.ts)_
361
377
 
362
378
  ## `lps snippet push [PATH]`
363
379
 
@@ -372,7 +388,7 @@ ARGUMENTS
372
388
 
373
389
  FLAGS
374
390
  -d, --dryRun Dry run - show what would happen without making changes
375
- -p, --plugin=<option> [default: code-snippets] WordPress snippet plugin to target
391
+ -p, --plugin=<option> WordPress snippet plugin to target (overrides loopress.json)
376
392
  <options: code-snippets|wpcode>
377
393
 
378
394
  GLOBAL FLAGS
@@ -393,59 +409,5 @@ EXAMPLES
393
409
  $ lps snippets push --plugin wpcode
394
410
  ```
395
411
 
396
- _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/snippet/push.ts)_
397
-
398
- ## `lps style pull`
399
-
400
- Pull Global Styles from WordPress
401
-
402
- ```
403
- USAGE
404
- $ lps style pull [--password <value>] [--url <value>] [--user <value>] [-d]
405
-
406
- FLAGS
407
- -d, --dryRun Dry run - show what would happen without making changes
408
-
409
- GLOBAL FLAGS
410
- --password=<value> WordPress application password (fallback; prefer `lps project config`)
411
- --url=<value> WordPress URL (fallback; prefer `lps project config`)
412
- --user=<value> WordPress username (fallback; prefer `lps project config`)
413
-
414
- DESCRIPTION
415
- Pull Global Styles from WordPress
416
-
417
- EXAMPLES
418
- $ lps styles pull
419
-
420
- $ lps styles pull --url http://example.com
421
- ```
422
-
423
- _See code: [src/commands/style/pull.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/style/pull.ts)_
424
-
425
- ## `lps style push`
426
-
427
- Push Global Styles to WordPress
428
-
429
- ```
430
- USAGE
431
- $ lps style push [--password <value>] [--url <value>] [--user <value>] [-d]
432
-
433
- FLAGS
434
- -d, --dryRun Dry run - show what would happen without making changes
435
-
436
- GLOBAL FLAGS
437
- --password=<value> WordPress application password (fallback; prefer `lps project config`)
438
- --url=<value> WordPress URL (fallback; prefer `lps project config`)
439
- --user=<value> WordPress username (fallback; prefer `lps project config`)
440
-
441
- DESCRIPTION
442
- Push Global Styles to WordPress
443
-
444
- EXAMPLES
445
- $ lps styles push
446
-
447
- $ lps styles push --url http://example.com
448
- ```
449
-
450
- _See code: [src/commands/style/push.ts](https://github.com/loopress/loopress/blob/v0.4.0/src/commands/style/push.ts)_
412
+ _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/push.ts)_
451
413
  <!-- commandsstop -->
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Init extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -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
+ }
@@ -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 description: string;
4
4
  static examples: string[];
5
5
  static flags: {
@@ -1,19 +1,20 @@
1
1
  import { confirm } from '@inquirer/prompts';
2
2
  import { Flags } from '@oclif/core';
3
3
  import got from 'got';
4
- import { LoopressCommand } from '../../lib/base.js';
4
+ import { PushCommand } from '../../lib/push-command.js';
5
5
  import { readLocalConfig } from '../../utils/loopress-config.js';
6
6
  import { diffPlugins } from '../../utils/plugins.js';
7
- export default class Push extends LoopressCommand {
7
+ export default class Push extends PushCommand {
8
8
  static description = 'Sync plugins on WordPress to match loopress.json';
9
9
  static examples = ['$ lps plugins push', '$ lps plugins push --dry-run'];
10
10
  static flags = {
11
- ...LoopressCommand.baseFlags,
11
+ ...PushCommand.baseFlags,
12
12
  'dry-run': Flags.boolean({ char: 'd', description: 'Show what would change without making changes' }),
13
13
  };
14
14
  async run() {
15
15
  const { flags } = await this.parse(Push);
16
16
  const dryRun = flags['dry-run'];
17
+ this.dryRun = dryRun;
17
18
  const { url } = this.siteConfig;
18
19
  const localConfig = await readLocalConfig();
19
20
  const manifest = localConfig.plugins;
@@ -96,6 +97,7 @@ export default class Push extends LoopressCommand {
96
97
  }
97
98
  await this.activatePlugin(url, headers, action.slug);
98
99
  }
100
+ await this.recordSuccess();
99
101
  }
100
102
  async activatePlugin(url, headers, slug) {
101
103
  try {
@@ -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
  }
@@ -1,8 +1,8 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import got from 'got';
3
- import { LoopressCommand } from '../../lib/base.js';
3
+ import { PushCommand } from '../../lib/push-command.js';
4
4
  import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
5
- export default class Push extends LoopressCommand {
5
+ export default class Push extends PushCommand {
6
6
  static args = {
7
7
  path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
8
8
  };
@@ -14,68 +14,86 @@ export default class Push extends LoopressCommand {
14
14
  '$ lps snippets push --plugin wpcode',
15
15
  ];
16
16
  static flags = {
17
- ...LoopressCommand.baseFlags,
17
+ ...PushCommand.baseFlags,
18
18
  dryRun: Flags.boolean({ char: 'd', description: 'Dry run - show what would happen without making changes' }),
19
19
  plugin: Flags.string({
20
20
  char: 'p',
21
- default: 'code-snippets',
22
- description: 'WordPress snippet plugin to target',
21
+ description: 'WordPress snippet plugin to target (overrides loopress.json)',
23
22
  options: ['code-snippets', 'wpcode'],
24
23
  }),
25
24
  };
26
25
  async run() {
27
26
  const { args, flags } = await this.parse(Push);
28
27
  const { dryRun, plugin } = flags;
28
+ this.dryRun = dryRun;
29
29
  const { url } = this.siteConfig;
30
30
  const path = await this.resolveSnippetsPath(args.path);
31
- this.log(`🚀 Pushing snippets to ${url} via ${plugin}`);
31
+ const resolvedPlugin = await this.resolveSnippetPlugin(plugin);
32
+ this.log(`🚀 Pushing snippets to ${url} via ${resolvedPlugin}`);
32
33
  this.log(`📂 From snippet path: ${path}`);
33
34
  this.log(`🔄 Dry run: ${dryRun ? 'yes' : 'no'}`);
35
+ let snippets = [];
34
36
  try {
35
- const snippets = await this.loadSnippets(path);
37
+ snippets = await this.loadSnippets(path);
36
38
  this.log(`✅ Found ${snippets.length} snippets to push`);
37
39
  const headers = await this.buildAuthHeaders();
38
- const adapter = getSnippetPlugin(plugin);
40
+ const adapter = getSnippetPlugin(resolvedPlugin);
39
41
  for (const snippet of snippets) {
40
- await this.pushSnippet(snippet, url, headers, dryRun, adapter);
42
+ await this.pushSnippet(snippet, { adapter, dryRun, headers, url });
41
43
  }
44
+ await this.recordSuccess();
42
45
  this.log('🎉 All snippets pushed successfully!');
43
46
  }
44
47
  catch (error) {
45
48
  this.error(error.message);
46
49
  }
47
50
  }
48
- async injectIdIntoFile(filePath, content, id) {
51
+ async injectIdIntoMeta(filePath, id) {
49
52
  const fs = await import('node:fs/promises');
50
- let updated;
51
- if (content.includes('/**')) {
52
- updated = content.replace('/**', `/**\n * id: ${id}`);
53
- }
54
- else if (content.includes('<!--')) {
55
- updated = content.replace('<!--', `<!--\n id: ${id}`);
53
+ const metaPath = filePath.replace(/\.[^.]+$/, '.json');
54
+ let meta = {};
55
+ try {
56
+ const existing = await fs.readFile(metaPath, 'utf8');
57
+ meta = JSON.parse(existing);
56
58
  }
57
- else {
58
- return;
59
+ catch (error) {
60
+ if (error.code !== 'ENOENT')
61
+ throw error;
59
62
  }
60
- await fs.writeFile(filePath, updated);
63
+ meta.id = id;
64
+ await fs.writeFile(metaPath, JSON.stringify(meta, null, 2) + '\n');
61
65
  }
62
66
  async loadSnippets(path) {
63
67
  const fs = await import('node:fs/promises');
64
68
  const snippets = [];
69
+ const SNIPPET_EXTENSIONS = new Set(['.css', '.html', '.js', '.php', '.txt']);
65
70
  try {
66
71
  const files = await fs.readdir(path);
67
72
  for (const file of files) {
68
- if (file.endsWith('.php')) {
69
- const filePath = `${path}/${file}`;
70
- const content = await fs.readFile(filePath, 'utf8');
71
- const meta = this.parseMetaFromContent(content);
72
- snippets.push({
73
- code: content,
74
- id: meta.id,
75
- name: meta.name ?? file.replace('.php', ''),
76
- path: filePath,
77
- });
73
+ const ext = file.slice(file.lastIndexOf('.'));
74
+ if (!SNIPPET_EXTENSIONS.has(ext))
75
+ continue;
76
+ const filePath = `${path}/${file}`;
77
+ const metaPath = filePath.slice(0, filePath.lastIndexOf('.')) + '.json';
78
+ const content = await fs.readFile(filePath, 'utf8');
79
+ let id;
80
+ let name;
81
+ try {
82
+ const metaContent = await fs.readFile(metaPath, 'utf8');
83
+ const meta = JSON.parse(metaContent);
84
+ id = meta.id ? Number(meta.id) : undefined;
85
+ name = meta.name ? String(meta.name) : undefined;
86
+ }
87
+ catch (error) {
88
+ if (error.code !== 'ENOENT')
89
+ throw error;
78
90
  }
91
+ snippets.push({
92
+ code: content,
93
+ id,
94
+ name: name ?? file.slice(0, file.lastIndexOf('.')),
95
+ path: filePath,
96
+ });
79
97
  }
80
98
  }
81
99
  catch (error) {
@@ -83,15 +101,8 @@ export default class Push extends LoopressCommand {
83
101
  }
84
102
  return snippets;
85
103
  }
86
- parseMetaFromContent(content) {
87
- const idMatch = content.match(/[\s*]*id:\s*(\d+)/);
88
- const nameMatch = content.match(/[\s*]*name:\s*(.+)/);
89
- return {
90
- id: idMatch ? Number(idMatch[1]) : undefined,
91
- name: nameMatch ? nameMatch[1].trim() : undefined,
92
- };
93
- }
94
- async pushSnippet(snippet, url, headers, dryRun, adapter) {
104
+ async pushSnippet(snippet, ctx) {
105
+ const { adapter, dryRun, headers, url } = ctx;
95
106
  if (dryRun) {
96
107
  this.log(`📝 [DRY RUN] Would push snippet: ${snippet.name}`);
97
108
  this.log(`📄 Code preview: ${snippet.code.slice(0, 100)}...`);
@@ -109,7 +120,7 @@ export default class Push extends LoopressCommand {
109
120
  this.log(`➕ Creating new snippet: ${snippet.name}`);
110
121
  const response = await got.post(endpoint, { headers, json: payload }).json();
111
122
  const created = adapter.fromRemote(response);
112
- await this.injectIdIntoFile(snippet.path, snippet.code, created.id);
123
+ await this.injectIdIntoMeta(snippet.path, created.id);
113
124
  this.log(`✅ Created: ${snippet.name} (id: ${created.id})`);
114
125
  }
115
126
  catch (error) {
@@ -30,10 +30,10 @@ export class AuthManager {
30
30
  }
31
31
  }
32
32
  getAuthFilePath() {
33
- return join(this.homeDir, '.lps', 'auth.json');
33
+ return join(this.homeDir, '.loopress', 'auth.json');
34
34
  }
35
35
  setAuth(auth) {
36
- const dir = join(this.homeDir, '.lps');
36
+ const dir = join(this.homeDir, '.loopress');
37
37
  if (!existsSync(dir))
38
38
  mkdirSync(dir, { recursive: true });
39
39
  const filePath = this.getAuthFilePath();
@@ -14,13 +14,13 @@ export class ProjectConfigManager {
14
14
  return ProjectConfigManager.instance;
15
15
  }
16
16
  ensureConfigDir() {
17
- const dir = join(this.homeDir, '.lps');
17
+ const dir = join(this.homeDir, '.loopress');
18
18
  if (!existsSync(dir)) {
19
19
  mkdirSync(dir, { recursive: true });
20
20
  }
21
21
  }
22
22
  getConfigFilePath() {
23
- return join(this.homeDir, '.lps', 'config.json');
23
+ return join(this.homeDir, '.loopress', 'config.json');
24
24
  }
25
25
  getCurrentEnv() {
26
26
  const project = this.getCurrentProject();
@@ -9,6 +9,6 @@ export declare abstract class LoopressCommand extends Command {
9
9
  protected siteConfig: EnvironmentConfig;
10
10
  buildAuthHeaders(): Promise<Record<string, string>>;
11
11
  init(): Promise<void>;
12
+ protected resolveSnippetPlugin(flag?: string): Promise<'code-snippets' | 'wpcode'>;
12
13
  protected resolveSnippetsPath(override?: string): Promise<string>;
13
- protected resolveStylesPath(override?: string): Promise<string>;
14
14
  }
package/dist/lib/base.js CHANGED
@@ -27,6 +27,18 @@ export class LoopressCommand extends Command {
27
27
  }
28
28
  async init() {
29
29
  await super.init();
30
+ const localConfig = await readLocalConfig();
31
+ if (localConfig.projectId) {
32
+ const project = configManager.getProject(localConfig.projectId);
33
+ if (!project) {
34
+ this.error(`Project "${localConfig.projectId}" (from loopress.json) not found. Run \`lps project config\` to configure it.`);
35
+ }
36
+ if (!project.currentEnv || !project.environments[project.currentEnv]) {
37
+ this.error(`Project "${localConfig.projectId}" has no active environment. Run \`lps project config\` to configure one.`);
38
+ }
39
+ this.siteConfig = project.environments[project.currentEnv];
40
+ return;
41
+ }
30
42
  const env = configManager.getCurrentEnv();
31
43
  if (env) {
32
44
  this.siteConfig = env;
@@ -34,16 +46,16 @@ export class LoopressCommand extends Command {
34
46
  }
35
47
  this.error('No environment configured. Run `lps project config` first.');
36
48
  }
37
- async resolveSnippetsPath(override) {
38
- if (override)
39
- return override;
49
+ async resolveSnippetPlugin(flag) {
50
+ if (flag)
51
+ return flag;
40
52
  const config = await readLocalConfig();
41
- return join(config.rootDir ?? '.', config.snippets ?? 'snippets');
53
+ return config.snippetPlugin ?? 'wpcode';
42
54
  }
43
- async resolveStylesPath(override) {
55
+ async resolveSnippetsPath(override) {
44
56
  if (override)
45
57
  return override;
46
58
  const config = await readLocalConfig();
47
- return join(config.rootDir ?? '.', config.styles ?? 'styles');
59
+ return join(config.rootDir ?? '.', config.snippetsDir ?? 'snippets');
48
60
  }
49
61
  }
@@ -0,0 +1,7 @@
1
+ import { LoopressCommand } from './base.js';
2
+ export declare abstract class PushCommand extends LoopressCommand {
3
+ protected dryRun: boolean;
4
+ catch(err: Error): Promise<void>;
5
+ protected recordDeployment(status: 'failure' | 'success'): Promise<void>;
6
+ protected recordSuccess(): Promise<void>;
7
+ }
@@ -0,0 +1,32 @@
1
+ import got from 'got';
2
+ import { authManager } from '../config/auth.manager.js';
3
+ import { LoopressCommand } from './base.js';
4
+ const API_URL = process.env.LPS_API_URL ?? 'https://api.loopress.dev';
5
+ export class PushCommand extends LoopressCommand {
6
+ dryRun = false;
7
+ async catch(err) {
8
+ if (!this.dryRun && this.siteConfig) {
9
+ await this.recordDeployment('failure');
10
+ }
11
+ return super.catch(err);
12
+ }
13
+ async recordDeployment(status) {
14
+ const token = process.env.LPS_TOKEN ?? authManager.getAuth()?.token ?? null;
15
+ if (!token)
16
+ return;
17
+ try {
18
+ await got.post(`${API_URL}/deployments`, {
19
+ headers: { Authorization: `Bearer ${token}` },
20
+ json: { status, url: this.siteConfig.url },
21
+ timeout: { request: 3000 },
22
+ });
23
+ }
24
+ catch {
25
+ // non-blocking: recording must never interrupt the push flow
26
+ }
27
+ }
28
+ async recordSuccess() {
29
+ if (!this.dryRun)
30
+ await this.recordDeployment('success');
31
+ }
32
+ }
@@ -1,8 +1,9 @@
1
1
  export interface LoopressLocalConfig {
2
2
  plugins?: Record<string, string>;
3
+ projectId?: string;
3
4
  rootDir?: string;
4
- snippets?: string;
5
- styles?: string;
5
+ snippetPlugin?: 'code-snippets' | 'wpcode';
6
+ snippetsDir?: string;
6
7
  }
7
8
  export declare function readLocalConfig(): Promise<LoopressLocalConfig>;
8
9
  export declare function writeLocalConfig(config: LoopressLocalConfig): Promise<void>;
@@ -31,7 +31,7 @@ class CodeSnippetsPlugin {
31
31
  }
32
32
  toPayload(name, code, path) {
33
33
  return {
34
- code,
34
+ code: code.replace(/^<\?php\s*/i, ''),
35
35
  desc: `Imported from ${path}`,
36
36
  name,
37
37
  tags: ['cli-import'],
@@ -1,5 +1,28 @@
1
1
  {
2
2
  "commands": {
3
+ "init": {
4
+ "aliases": [],
5
+ "args": {},
6
+ "description": "Initialize a loopress.json config file in the current directory",
7
+ "examples": [
8
+ "$ lps init"
9
+ ],
10
+ "flags": {},
11
+ "hasDynamicHelp": false,
12
+ "hiddenAliases": [],
13
+ "id": "init",
14
+ "pluginAlias": "@loopress/cli",
15
+ "pluginName": "@loopress/cli",
16
+ "pluginType": "core",
17
+ "strict": true,
18
+ "enableJsonFlag": false,
19
+ "isESM": true,
20
+ "relativePath": [
21
+ "dist",
22
+ "commands",
23
+ "init.js"
24
+ ]
25
+ },
3
26
  "login": {
4
27
  "aliases": [],
5
28
  "args": {},
@@ -151,7 +174,6 @@
151
174
  "pluginName": "@loopress/cli",
152
175
  "pluginType": "core",
153
176
  "strict": true,
154
- "enableJsonFlag": false,
155
177
  "isESM": true,
156
178
  "relativePath": [
157
179
  "dist",
@@ -415,9 +437,8 @@
415
437
  },
416
438
  "plugin": {
417
439
  "char": "p",
418
- "description": "WordPress snippet plugin to target",
440
+ "description": "WordPress snippet plugin to target (overrides loopress.json)",
419
441
  "name": "plugin",
420
- "default": "code-snippets",
421
442
  "hasDynamicHelp": false,
422
443
  "multiple": false,
423
444
  "options": [
@@ -492,9 +513,8 @@
492
513
  },
493
514
  "plugin": {
494
515
  "char": "p",
495
- "description": "WordPress snippet plugin to target",
516
+ "description": "WordPress snippet plugin to target (overrides loopress.json)",
496
517
  "name": "plugin",
497
- "default": "code-snippets",
498
518
  "hasDynamicHelp": false,
499
519
  "multiple": false,
500
520
  "options": [
@@ -569,9 +589,8 @@
569
589
  },
570
590
  "plugin": {
571
591
  "char": "p",
572
- "description": "WordPress snippet plugin to target",
592
+ "description": "WordPress snippet plugin to target (overrides loopress.json)",
573
593
  "name": "plugin",
574
- "default": "code-snippets",
575
594
  "hasDynamicHelp": false,
576
595
  "multiple": false,
577
596
  "options": [
@@ -588,7 +607,6 @@
588
607
  "pluginName": "@loopress/cli",
589
608
  "pluginType": "core",
590
609
  "strict": true,
591
- "enableJsonFlag": false,
592
610
  "isESM": true,
593
611
  "relativePath": [
594
612
  "dist",
@@ -596,121 +614,7 @@
596
614
  "snippet",
597
615
  "push.js"
598
616
  ]
599
- },
600
- "style:pull": {
601
- "aliases": [],
602
- "args": {},
603
- "description": "Pull Global Styles from WordPress",
604
- "examples": [
605
- "$ lps styles pull",
606
- "$ lps styles pull --url http://example.com"
607
- ],
608
- "flags": {
609
- "password": {
610
- "description": "WordPress application password (fallback; prefer `lps project config`)",
611
- "helpGroup": "GLOBAL",
612
- "name": "password",
613
- "hasDynamicHelp": false,
614
- "multiple": false,
615
- "type": "option"
616
- },
617
- "url": {
618
- "description": "WordPress URL (fallback; prefer `lps project config`)",
619
- "helpGroup": "GLOBAL",
620
- "name": "url",
621
- "hasDynamicHelp": false,
622
- "multiple": false,
623
- "type": "option"
624
- },
625
- "user": {
626
- "description": "WordPress username (fallback; prefer `lps project config`)",
627
- "helpGroup": "GLOBAL",
628
- "name": "user",
629
- "hasDynamicHelp": false,
630
- "multiple": false,
631
- "type": "option"
632
- },
633
- "dryRun": {
634
- "char": "d",
635
- "description": "Dry run - show what would happen without making changes",
636
- "name": "dryRun",
637
- "allowNo": false,
638
- "type": "boolean"
639
- }
640
- },
641
- "hasDynamicHelp": false,
642
- "hiddenAliases": [],
643
- "id": "style:pull",
644
- "pluginAlias": "@loopress/cli",
645
- "pluginName": "@loopress/cli",
646
- "pluginType": "core",
647
- "strict": true,
648
- "enableJsonFlag": false,
649
- "isESM": true,
650
- "relativePath": [
651
- "dist",
652
- "commands",
653
- "style",
654
- "pull.js"
655
- ]
656
- },
657
- "style:push": {
658
- "aliases": [],
659
- "args": {},
660
- "description": "Push Global Styles to WordPress",
661
- "examples": [
662
- "$ lps styles push",
663
- "$ lps styles push --url http://example.com"
664
- ],
665
- "flags": {
666
- "password": {
667
- "description": "WordPress application password (fallback; prefer `lps project config`)",
668
- "helpGroup": "GLOBAL",
669
- "name": "password",
670
- "hasDynamicHelp": false,
671
- "multiple": false,
672
- "type": "option"
673
- },
674
- "url": {
675
- "description": "WordPress URL (fallback; prefer `lps project config`)",
676
- "helpGroup": "GLOBAL",
677
- "name": "url",
678
- "hasDynamicHelp": false,
679
- "multiple": false,
680
- "type": "option"
681
- },
682
- "user": {
683
- "description": "WordPress username (fallback; prefer `lps project config`)",
684
- "helpGroup": "GLOBAL",
685
- "name": "user",
686
- "hasDynamicHelp": false,
687
- "multiple": false,
688
- "type": "option"
689
- },
690
- "dryRun": {
691
- "char": "d",
692
- "description": "Dry run - show what would happen without making changes",
693
- "name": "dryRun",
694
- "allowNo": false,
695
- "type": "boolean"
696
- }
697
- },
698
- "hasDynamicHelp": false,
699
- "hiddenAliases": [],
700
- "id": "style:push",
701
- "pluginAlias": "@loopress/cli",
702
- "pluginName": "@loopress/cli",
703
- "pluginType": "core",
704
- "strict": true,
705
- "enableJsonFlag": false,
706
- "isESM": true,
707
- "relativePath": [
708
- "dist",
709
- "commands",
710
- "style",
711
- "push.js"
712
- ]
713
617
  }
714
618
  },
715
- "version": "0.4.0"
619
+ "version": "0.5.0"
716
620
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loopress/cli",
3
3
  "description": "CLI tool for syncing WordPress CodeSnippets, styles, and menus via the REST API",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "author": "jean-smaug",
6
6
  "bin": {
7
7
  "loopress": "bin/run.js",
@@ -12,31 +12,27 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@inquirer/prompts": "^8.5.2",
15
- "@oclif/core": "^4.11.7",
16
- "@oclif/plugin-help": "^6.2.50",
17
- "@oclif/plugin-plugins": "^5.4.72",
15
+ "@oclif/core": "^4.11.11",
16
+ "@oclif/plugin-help": "^6.2.52",
17
+ "@oclif/plugin-plugins": "^5.4.80",
18
18
  "glob": "13.0.6",
19
- "got": "^15.0.5",
19
+ "got": "^15.0.7",
20
20
  "slugify": "^1.6.9"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@eslint/compat": "2.1.0",
24
24
  "@oclif/prettier-config": "^0.2.1",
25
- "@oclif/test": "^4",
26
- "@types/chai": "^5.2.3",
27
- "@types/mocha": "^10.0.10",
28
- "@types/node": "25.9.3",
29
- "chai": "^6.2.2",
25
+ "@oclif/test": "^4.1.20",
26
+ "@types/node": "26.0.1",
30
27
  "eslint": "^9",
31
- "eslint-config-oclif": "^6",
28
+ "eslint-config-oclif": "^6.0.174",
32
29
  "eslint-config-prettier": "^10",
33
- "mocha": "^11.7.6",
34
- "oclif": "^4",
35
- "prettier": "3.8.4",
30
+ "oclif": "^4.23.22",
31
+ "prettier": "3.9.1",
36
32
  "shx": "^0.4.0",
37
- "ts-node": "^10",
38
33
  "tsx": "4.22.4",
39
- "typescript": "^6.0.3"
34
+ "typescript": "^6.0.3",
35
+ "vitest": "^3.2.4"
40
36
  },
41
37
  "engines": {
42
38
  "node": ">=18.0.0"
@@ -80,7 +76,7 @@
80
76
  "build": "shx rm -rf dist && tsc -b",
81
77
  "lint": "eslint",
82
78
  "posttest": "pnpm run lint",
83
- "test": "mocha --forbid-only \"test/**/*.test.ts\" --reporter spec --full-trace --verbose",
79
+ "test": "vitest run",
84
80
  "version": "oclif readme && git add README.md",
85
81
  "format": "prettier . --write"
86
82
  }
@@ -1,12 +0,0 @@
1
- import { LoopressCommand } from '../../lib/base.js';
2
- export default class Pull extends LoopressCommand {
3
- static description: string;
4
- static examples: string[];
5
- static flags: {
6
- dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
- password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
- url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
- user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
- };
11
- run(): Promise<void>;
12
- }
@@ -1,52 +0,0 @@
1
- import { Flags } from '@oclif/core';
2
- import got from 'got';
3
- import { LoopressCommand } from '../../lib/base.js';
4
- export default class Pull extends LoopressCommand {
5
- static description = 'Pull Global Styles from WordPress';
6
- static examples = ['$ lps styles pull', '$ lps styles pull --url http://example.com'];
7
- static flags = {
8
- ...LoopressCommand.baseFlags,
9
- dryRun: Flags.boolean({ char: 'd', description: 'Dry run - show what would happen without making changes' }),
10
- };
11
- async run() {
12
- const { flags } = await this.parse(Pull);
13
- const { dryRun } = flags;
14
- const { url } = this.siteConfig;
15
- const stylesDir = await this.resolveStylesPath();
16
- const outputPath = `${stylesDir}/global-styles.json`;
17
- this.log(`đŸ“Ĩ Pulling Global Styles from ${url}`);
18
- this.log(`📂 Target file: ${outputPath}`);
19
- this.log(`🔄 Dry run: ${dryRun ? 'yes' : 'no'}`);
20
- try {
21
- const headers = await this.buildAuthHeaders();
22
- this.log('🔍 Finding active theme...');
23
- const themes = await got.get(`${url}/wp-json/wp/v2/themes?status=active`, { headers }).json();
24
- if (!themes || themes.length === 0) {
25
- this.error('❌ No active theme found.');
26
- }
27
- const activeTheme = themes[0];
28
- const globalStylesEndpoint = activeTheme._links['wp:user-global-styles'][0].href;
29
- if (!globalStylesEndpoint) {
30
- this.error(`❌ Active theme "${activeTheme.name}" does not have global styles endpoint.`);
31
- }
32
- const globalStyles = await got.get(globalStylesEndpoint, { headers }).json();
33
- const dataToSave = {
34
- id: globalStyles.id,
35
- settings: globalStyles.settings,
36
- styles: globalStyles.styles,
37
- };
38
- if (dryRun) {
39
- this.log(`📝 [DRY RUN] Would pull styles and settings for ID: ${globalStyles.id}`);
40
- this.log(`📄 Data preview: ${JSON.stringify(dataToSave).slice(0, 100)}...`);
41
- return;
42
- }
43
- const fs = await import('node:fs/promises');
44
- await fs.mkdir(stylesDir, { recursive: true });
45
- await fs.writeFile(outputPath, JSON.stringify(dataToSave, null, 2));
46
- this.log(`✅ Successfully pulled global styles to ${outputPath}`);
47
- }
48
- catch (error) {
49
- this.error(`❌ Error pulling global styles: ${error.message}`);
50
- }
51
- }
52
- }
@@ -1,13 +0,0 @@
1
- import { LoopressCommand } from '../../lib/base.js';
2
- export default class Push extends LoopressCommand {
3
- static description: string;
4
- static examples: string[];
5
- static flags: {
6
- dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
- password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
- url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
- user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
- };
11
- run(): Promise<void>;
12
- private readOrFetchGlobalStyles;
13
- }
@@ -1,78 +0,0 @@
1
- import { Flags } from '@oclif/core';
2
- import { glob } from 'glob';
3
- import got from 'got';
4
- import { LoopressCommand } from '../../lib/base.js';
5
- export default class Push extends LoopressCommand {
6
- static description = 'Push Global Styles to WordPress';
7
- static examples = ['$ lps styles push', '$ lps styles push --url http://example.com'];
8
- static flags = {
9
- ...LoopressCommand.baseFlags,
10
- dryRun: Flags.boolean({ char: 'd', description: 'Dry run - show what would happen without making changes' }),
11
- };
12
- async run() {
13
- const { flags } = await this.parse(Push);
14
- const { dryRun } = flags;
15
- const { url } = this.siteConfig;
16
- const stylesDir = await this.resolveStylesPath();
17
- const jsonPath = `${stylesDir}/global-styles.json`;
18
- this.log(`📤 Pushing Global Styles to ${url}`);
19
- this.log(`📂 From directory: ${stylesDir}`);
20
- this.log(`🔄 Dry run: ${dryRun ? 'yes' : 'no'}`);
21
- try {
22
- const fs = await import('node:fs/promises');
23
- const headers = await this.buildAuthHeaders();
24
- const data = await this.readOrFetchGlobalStyles(jsonPath, url, headers, fs);
25
- this.log('🎨 Bundling CSS files in memory...');
26
- const cssFiles = await glob(`${stylesDir}/**/*.css`);
27
- let bundledCss = '';
28
- if (cssFiles.length > 0) {
29
- const cssContents = await Promise.all(cssFiles.map((file) => fs.readFile(file, 'utf8')));
30
- bundledCss = cssContents.join('\n').trim();
31
- this.log(`✨ Bundled ${cssFiles.length} CSS files`);
32
- }
33
- else {
34
- this.log(`âš ī¸ No CSS files found in ${stylesDir}/**/*.css`);
35
- }
36
- const endpoint = `${url}/wp-json/wp/v2/global-styles/${data.id}`;
37
- const payload = {
38
- settings: data.settings,
39
- styles: {
40
- ...data.styles,
41
- ...(bundledCss ? { css: bundledCss } : {}),
42
- },
43
- };
44
- if (dryRun) {
45
- this.log(`📝 [DRY RUN] Would push to ${endpoint}`);
46
- this.log(`📄 Payload preview: ${JSON.stringify(payload).slice(0, 100)}...`);
47
- return;
48
- }
49
- await got.post(endpoint, { headers, json: payload });
50
- this.log(`✅ Successfully pushed global styles to ID: ${data.id}`);
51
- }
52
- catch (error) {
53
- this.error(`❌ Error pushing global styles: ${error.message}`);
54
- }
55
- }
56
- async readOrFetchGlobalStyles(jsonPath, url, headers, fs) {
57
- try {
58
- const content = await fs.readFile(jsonPath, 'utf8');
59
- return JSON.parse(content);
60
- }
61
- catch (error) {
62
- if (error.code !== 'ENOENT')
63
- throw error;
64
- }
65
- this.log('â„šī¸ No local cache found — fetching global styles from WordPress...');
66
- const themes = await got.get(`${url}/wp-json/wp/v2/themes?status=active`, { headers }).json();
67
- if (!themes || themes.length === 0) {
68
- this.error('❌ No active theme found.');
69
- }
70
- const globalStylesEndpoint = themes[0]._links['wp:user-global-styles'][0].href;
71
- const globalStyles = await got.get(globalStylesEndpoint, { headers }).json();
72
- const data = { id: globalStyles.id, settings: globalStyles.settings, styles: globalStyles.styles };
73
- await fs.mkdir(jsonPath.replace(/\/[^/]+$/, ''), { recursive: true });
74
- await fs.writeFile(jsonPath, JSON.stringify(data, null, 2));
75
- this.log(`💾 Cached to ${jsonPath}`);
76
- return data;
77
- }
78
- }