@loopress/cli 0.4.0 → 0.6.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 +122 -102
- package/dist/commands/{style → composer}/pull.d.ts +2 -2
- package/dist/commands/composer/pull.js +33 -0
- package/dist/commands/{style → composer}/push.d.ts +3 -4
- package/dist/commands/composer/push.js +49 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/plugin/{require.d.ts → add.d.ts} +3 -1
- package/dist/commands/plugin/{require.js → add.js} +29 -8
- package/dist/commands/plugin/pull.js +10 -1
- package/dist/commands/plugin/push.d.ts +2 -2
- package/dist/commands/plugin/push.js +15 -4
- package/dist/commands/project/config.js +2 -2
- package/dist/commands/snippet/list.d.ts +1 -1
- package/dist/commands/snippet/list.js +3 -3
- package/dist/commands/snippet/pull.d.ts +4 -1
- package/dist/commands/snippet/pull.js +26 -37
- package/dist/commands/snippet/push.d.ts +4 -5
- package/dist/commands/snippet/push.js +50 -39
- package/dist/config/auth.manager.js +2 -2
- package/dist/config/project-config.manager.js +2 -2
- package/dist/lib/base.d.ts +1 -1
- package/dist/lib/base.js +18 -6
- package/dist/lib/push-command.d.ts +7 -0
- package/dist/lib/push-command.js +32 -0
- package/dist/utils/composer.d.ts +7 -0
- package/dist/utils/composer.js +33 -0
- package/dist/utils/loopress-config.d.ts +3 -2
- package/dist/utils/snippet-plugin.js +1 -1
- package/oclif.manifest.json +238 -220
- package/package.json +17 -17
- package/dist/commands/style/pull.js +0 -52
- package/dist/commands/style/push.js +0 -78
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import got from 'got';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { LoopressCommand } from '../../lib/base.js';
|
|
4
5
|
import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
|
|
5
6
|
const WP_ORG_API = 'https://api.wordpress.org/plugins/info/1.2/';
|
|
@@ -24,26 +25,46 @@ export async function resolvePluginVersion(slug, version) {
|
|
|
24
25
|
throw new Error(`Plugin "${slug}" not found on WordPress.org.`);
|
|
25
26
|
return info.version;
|
|
26
27
|
}
|
|
27
|
-
export default class
|
|
28
|
+
export default class Add extends LoopressCommand {
|
|
28
29
|
static args = {
|
|
29
|
-
slug: Args.string({ description: 'Plugin slug (WordPress.org)', required: true }),
|
|
30
|
+
slug: Args.string({ description: 'Plugin slug (WordPress.org) or Composer package (vendor/package)', required: true }),
|
|
30
31
|
version: Args.string({ description: 'Version to pin (default: latest)' }),
|
|
31
32
|
};
|
|
32
|
-
static description = 'Add a plugin to loopress.json
|
|
33
|
+
static description = 'Add a plugin to loopress.json (WordPress.org) or run composer require (vendor/package)';
|
|
33
34
|
static examples = [
|
|
34
|
-
'$ lps
|
|
35
|
-
'$ lps
|
|
36
|
-
'$ lps
|
|
35
|
+
'$ lps plugin add woocommerce',
|
|
36
|
+
'$ lps plugin add woocommerce 8.9.1',
|
|
37
|
+
'$ lps plugin add wpackagist-plugin/advanced-custom-fields',
|
|
38
|
+
'$ lps plugin add contact-form-7 --dry-run',
|
|
37
39
|
];
|
|
38
40
|
static flags = {
|
|
39
41
|
...LoopressCommand.baseFlags,
|
|
40
42
|
'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be written without making changes' }),
|
|
41
43
|
};
|
|
42
44
|
async run() {
|
|
43
|
-
const { args, flags } = await this.parse(
|
|
45
|
+
const { args, flags } = await this.parse(Add);
|
|
44
46
|
const dryRun = flags['dry-run'];
|
|
45
47
|
const { slug } = args;
|
|
46
48
|
const requestedVersion = args.version ?? 'latest';
|
|
49
|
+
if (slug.includes('/')) {
|
|
50
|
+
await this.requireComposerPackage(slug, requestedVersion, dryRun);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
await this.requireWpOrgPlugin(slug, requestedVersion, dryRun);
|
|
54
|
+
}
|
|
55
|
+
async requireComposerPackage(pkg, version, dryRun) {
|
|
56
|
+
const composerArg = version === 'latest' ? pkg : `${pkg}:${version}`;
|
|
57
|
+
this.log(`Running: composer require ${composerArg}`);
|
|
58
|
+
if (dryRun) {
|
|
59
|
+
this.log(`[dry-run] Would run: composer require ${composerArg}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const result = spawnSync('composer', ['require', composerArg], { stdio: 'inherit' });
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
this.error('composer require failed. Make sure Composer is installed and accessible.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async requireWpOrgPlugin(slug, requestedVersion, dryRun) {
|
|
47
68
|
this.log(`Resolving ${slug}@${requestedVersion}...`);
|
|
48
69
|
let resolvedVersion;
|
|
49
70
|
try {
|
|
@@ -56,7 +77,7 @@ export default class Require extends LoopressCommand {
|
|
|
56
77
|
const localConfig = await readLocalConfig();
|
|
57
78
|
const existing = localConfig.plugins ?? {};
|
|
58
79
|
if (existing[slug] === resolvedVersion) {
|
|
59
|
-
this.log(`${slug}@${resolvedVersion} is already in loopress.json
|
|
80
|
+
this.log(`${slug}@${resolvedVersion} is already in loopress.json, nothing to do.`);
|
|
60
81
|
return;
|
|
61
82
|
}
|
|
62
83
|
const updated = existing[slug] !== undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import got from 'got';
|
|
3
3
|
import { LoopressCommand } from '../../lib/base.js';
|
|
4
|
+
import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
|
|
4
5
|
import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
|
|
5
6
|
import { mergePluginManifest } from '../../utils/plugins.js';
|
|
6
7
|
export default class Pull extends LoopressCommand {
|
|
@@ -17,7 +18,15 @@ export default class Pull extends LoopressCommand {
|
|
|
17
18
|
this.log(`Pulling plugins from ${url}`);
|
|
18
19
|
const headers = await this.buildAuthHeaders();
|
|
19
20
|
const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
|
|
20
|
-
const
|
|
21
|
+
const composerJson = await readComposerJson();
|
|
22
|
+
const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
|
|
23
|
+
const incoming = Object.fromEntries(installed.filter((p) => !composerSlugs.includes(p.slug)).map((p) => [p.slug, p.version]));
|
|
24
|
+
if (composerSlugs.length > 0) {
|
|
25
|
+
const found = installed.filter((p) => composerSlugs.includes(p.slug)).map((p) => p.slug);
|
|
26
|
+
if (found.length > 0) {
|
|
27
|
+
this.log(`Skipping ${found.length} Composer-managed ${found.length === 1 ? 'plugin' : 'plugins'}: ${found.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
const localConfig = await readLocalConfig();
|
|
22
31
|
const { added, merged, updated } = mergePluginManifest(localConfig.plugins ?? {}, incoming);
|
|
23
32
|
if (dryRun) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class Push extends
|
|
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,29 +1,39 @@
|
|
|
1
1
|
import { confirm } from '@inquirer/prompts';
|
|
2
2
|
import { Flags } from '@oclif/core';
|
|
3
3
|
import got from 'got';
|
|
4
|
-
import {
|
|
4
|
+
import { PushCommand } from '../../lib/push-command.js';
|
|
5
|
+
import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
|
|
5
6
|
import { readLocalConfig } from '../../utils/loopress-config.js';
|
|
6
7
|
import { diffPlugins } from '../../utils/plugins.js';
|
|
7
|
-
export default class Push extends
|
|
8
|
+
export default class Push extends PushCommand {
|
|
8
9
|
static description = 'Sync plugins on WordPress to match loopress.json';
|
|
9
10
|
static examples = ['$ lps plugins push', '$ lps plugins push --dry-run'];
|
|
10
11
|
static flags = {
|
|
11
|
-
...
|
|
12
|
+
...PushCommand.baseFlags,
|
|
12
13
|
'dry-run': Flags.boolean({ char: 'd', description: 'Show what would change without making changes' }),
|
|
13
14
|
};
|
|
14
15
|
async run() {
|
|
15
16
|
const { flags } = await this.parse(Push);
|
|
16
17
|
const dryRun = flags['dry-run'];
|
|
18
|
+
this.dryRun = dryRun;
|
|
17
19
|
const { url } = this.siteConfig;
|
|
18
20
|
const localConfig = await readLocalConfig();
|
|
19
21
|
const manifest = localConfig.plugins;
|
|
20
22
|
if (!manifest || Object.keys(manifest).length === 0) {
|
|
21
23
|
this.error('No plugins found in loopress.json. Run `lps plugins pull` first.');
|
|
22
24
|
}
|
|
25
|
+
const composerJson = await readComposerJson();
|
|
26
|
+
const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
|
|
27
|
+
const filteredManifest = Object.fromEntries(Object.entries(manifest).filter(([slug]) => !composerSlugs.includes(slug)));
|
|
28
|
+
const skipped = composerSlugs.filter((slug) => slug in manifest);
|
|
29
|
+
if (skipped.length > 0) {
|
|
30
|
+
this.log(`Skipping ${skipped.length} Composer-managed ${skipped.length === 1 ? 'plugin' : 'plugins'}: ${skipped.join(', ')}`);
|
|
31
|
+
this.log('Run `lps composer push` to deploy them.');
|
|
32
|
+
}
|
|
23
33
|
this.log(`Pushing plugins to ${url}`);
|
|
24
34
|
const headers = await this.buildAuthHeaders();
|
|
25
35
|
const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
|
|
26
|
-
const { drifted, toActivate, toInstall } = diffPlugins(
|
|
36
|
+
const { drifted, toActivate, toInstall } = diffPlugins(filteredManifest, installed);
|
|
27
37
|
if (toInstall.length === 0 && toActivate.length === 0 && drifted.length === 0) {
|
|
28
38
|
this.log('Everything is already in sync.');
|
|
29
39
|
return;
|
|
@@ -96,6 +106,7 @@ export default class Push extends LoopressCommand {
|
|
|
96
106
|
}
|
|
97
107
|
await this.activatePlugin(url, headers, action.slug);
|
|
98
108
|
}
|
|
109
|
+
await this.recordSuccess();
|
|
99
110
|
}
|
|
100
111
|
async activatePlugin(url, headers, slug) {
|
|
101
112
|
try {
|
|
@@ -17,9 +17,9 @@ export default class Config extends Command {
|
|
|
17
17
|
});
|
|
18
18
|
const envChoice = await select({
|
|
19
19
|
choices: [
|
|
20
|
-
{ name: '
|
|
20
|
+
{ name: 'local', value: 'local' },
|
|
21
21
|
{ name: 'staging', value: 'staging' },
|
|
22
|
-
{ name: '
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 {
|
|
2
|
-
export default class Push extends
|
|
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
|
|
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 {
|
|
3
|
+
import { PushCommand } from '../../lib/push-command.js';
|
|
4
4
|
import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
|
|
5
|
-
export default class Push extends
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
40
|
+
const adapter = getSnippetPlugin(resolvedPlugin);
|
|
39
41
|
for (const snippet of snippets) {
|
|
40
|
-
await this.pushSnippet(snippet,
|
|
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
|
|
51
|
+
async injectIdIntoMeta(filePath, id) {
|
|
49
52
|
const fs = await import('node:fs/promises');
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.code !== 'ENOENT')
|
|
61
|
+
throw error;
|
|
59
62
|
}
|
|
60
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
87
|
-
const
|
|
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.
|
|
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, '.
|
|
33
|
+
return join(this.homeDir, '.loopress', 'auth.json');
|
|
34
34
|
}
|
|
35
35
|
setAuth(auth) {
|
|
36
|
-
const dir = join(this.homeDir, '.
|
|
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, '.
|
|
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, '.
|
|
23
|
+
return join(this.homeDir, '.loopress', 'config.json');
|
|
24
24
|
}
|
|
25
25
|
getCurrentEnv() {
|
|
26
26
|
const project = this.getCurrentProject();
|
package/dist/lib/base.d.ts
CHANGED
|
@@ -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
|
|
38
|
-
if (
|
|
39
|
-
return
|
|
49
|
+
async resolveSnippetPlugin(flag) {
|
|
50
|
+
if (flag)
|
|
51
|
+
return flag;
|
|
40
52
|
const config = await readLocalConfig();
|
|
41
|
-
return
|
|
53
|
+
return config.snippetPlugin ?? 'wpcode';
|
|
42
54
|
}
|
|
43
|
-
async
|
|
55
|
+
async resolveSnippetsPath(override) {
|
|
44
56
|
if (override)
|
|
45
57
|
return override;
|
|
46
58
|
const config = await readLocalConfig();
|
|
47
|
-
return join(config.rootDir ?? '.', config.
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ComposerJson {
|
|
2
|
+
require?: Record<string, string>;
|
|
3
|
+
'require-dev'?: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export declare function readComposerJson(): Promise<ComposerJson | null>;
|
|
6
|
+
export declare function readComposerLock(): Promise<null | string>;
|
|
7
|
+
export declare function getComposerManagedSlugs(composerJson: ComposerJson): string[];
|