@loopress/cli 0.1.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/LICENSE +373 -0
- package/README.md +676 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/base.d.ts +14 -0
- package/dist/commands/base.js +49 -0
- package/dist/commands/login.d.ts +8 -0
- package/dist/commands/login.js +106 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/logout.js +15 -0
- package/dist/commands/project/config.d.ts +6 -0
- package/dist/commands/project/config.js +92 -0
- package/dist/commands/project/list.d.ts +6 -0
- package/dist/commands/project/list.js +31 -0
- package/dist/commands/project/remove-env.d.ts +6 -0
- package/dist/commands/project/remove-env.js +33 -0
- package/dist/commands/project/remove.d.ts +6 -0
- package/dist/commands/project/remove.js +34 -0
- package/dist/commands/project/switch-env.d.ts +6 -0
- package/dist/commands/project/switch-env.js +33 -0
- package/dist/commands/project/switch.d.ts +6 -0
- package/dist/commands/project/switch.js +34 -0
- package/dist/commands/snippets/list.d.ts +13 -0
- package/dist/commands/snippets/list.js +59 -0
- package/dist/commands/snippets/pull.d.ts +16 -0
- package/dist/commands/snippets/pull.js +58 -0
- package/dist/commands/snippets/push.d.ts +18 -0
- package/dist/commands/snippets/push.js +97 -0
- package/dist/commands/styles/pull.d.ts +12 -0
- package/dist/commands/styles/pull.js +52 -0
- package/dist/commands/styles/push.d.ts +13 -0
- package/dist/commands/styles/push.js +78 -0
- package/dist/config/auth.manager.d.ts +16 -0
- package/dist/config/auth.manager.js +45 -0
- package/dist/config/project-config.manager.d.ts +28 -0
- package/dist/config/project-config.manager.js +130 -0
- package/dist/config/types.d.ts +16 -0
- package/dist/config/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/menu.d.ts +7 -0
- package/dist/types/menu.js +1 -0
- package/dist/types/snippet.d.ts +6 -0
- package/dist/types/snippet.js +1 -0
- package/dist/utils/loopress-config.d.ts +6 -0
- package/dist/utils/loopress-config.js +14 -0
- package/dist/utils/snippet-plugin.d.ts +15 -0
- package/dist/utils/snippet-plugin.js +58 -0
- package/oclif.manifest.json +578 -0
- package/package.json +85 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LoopressCommand } from '../base.js';
|
|
2
|
+
export default class Push extends LoopressCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
plugin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
private loadSnippets;
|
|
17
|
+
private pushSnippet;
|
|
18
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import got from 'got';
|
|
3
|
+
import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
|
|
4
|
+
import { LoopressCommand } from '../base.js';
|
|
5
|
+
export default class Push extends LoopressCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Push snippets to WordPress';
|
|
10
|
+
static examples = [
|
|
11
|
+
'$ lps snippets push',
|
|
12
|
+
'$ lps snippets push --url http://example.com',
|
|
13
|
+
'$ lps snippets push --path ./snippets',
|
|
14
|
+
'$ lps snippets push --plugin wpcode',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...LoopressCommand.baseFlags,
|
|
18
|
+
dryRun: Flags.boolean({ char: 'd', description: 'Dry run - show what would happen without making changes' }),
|
|
19
|
+
plugin: Flags.string({
|
|
20
|
+
char: 'p',
|
|
21
|
+
default: 'code-snippets',
|
|
22
|
+
description: 'WordPress snippet plugin to target',
|
|
23
|
+
options: ['code-snippets', 'wpcode'],
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(Push);
|
|
28
|
+
const { dryRun, plugin } = flags;
|
|
29
|
+
const { url } = this.siteConfig;
|
|
30
|
+
const path = await this.resolveSnippetsPath(args.path);
|
|
31
|
+
this.log(`🚀 Pushing snippets to ${url} via ${plugin}`);
|
|
32
|
+
this.log(`📂 From snippet path: ${path}`);
|
|
33
|
+
this.log(`🔄 Dry run: ${dryRun ? 'yes' : 'no'}`);
|
|
34
|
+
try {
|
|
35
|
+
const snippets = await this.loadSnippets(path);
|
|
36
|
+
this.log(`✅ Found ${snippets.length} snippets to push`);
|
|
37
|
+
const headers = await this.buildAuthHeaders();
|
|
38
|
+
const adapter = getSnippetPlugin(plugin);
|
|
39
|
+
for (const snippet of snippets) {
|
|
40
|
+
await this.pushSnippet(snippet, url, headers, dryRun, adapter);
|
|
41
|
+
}
|
|
42
|
+
this.log('🎉 All snippets pushed successfully!');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.error(error.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async loadSnippets(path) {
|
|
49
|
+
const fs = await import('node:fs/promises');
|
|
50
|
+
const snippets = [];
|
|
51
|
+
try {
|
|
52
|
+
const files = await fs.readdir(path);
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
if (file.endsWith('.php')) {
|
|
55
|
+
const filePath = `${path}/${file}`;
|
|
56
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
57
|
+
snippets.push({
|
|
58
|
+
code: content,
|
|
59
|
+
name: file.replace('.php', ''),
|
|
60
|
+
path: filePath,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.error(`❌ Error loading snippets: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
return snippets;
|
|
69
|
+
}
|
|
70
|
+
async pushSnippet(snippet, url, headers, dryRun, adapter) {
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
this.log(`📝 [DRY RUN] Would push snippet: ${snippet.name}`);
|
|
73
|
+
this.log(`📄 Code preview: ${snippet.code.slice(0, 100)}...`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const endpoint = adapter.endpoint(url);
|
|
78
|
+
const remoteList = await got.get(endpoint, { headers }).json();
|
|
79
|
+
const existing = remoteList
|
|
80
|
+
.map((r) => adapter.fromRemote(r))
|
|
81
|
+
.find((s) => s.name === snippet.name);
|
|
82
|
+
const payload = adapter.toPayload(snippet.name, snippet.code, snippet.path);
|
|
83
|
+
if (existing) {
|
|
84
|
+
this.log(`🔄 Updating existing snippet: ${snippet.name}`);
|
|
85
|
+
await got.put(`${endpoint}/${existing.id}`, { headers, json: payload });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.log(`➕ Creating new snippet: ${snippet.name}`);
|
|
89
|
+
await got.post(endpoint, { headers, json: payload });
|
|
90
|
+
}
|
|
91
|
+
this.log(`✅ ${existing ? 'Updated' : 'Created'}: ${snippet.name}`);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.error(`❌ Error pushing snippet ${snippet.name}: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LoopressCommand } from '../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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import got from 'got';
|
|
3
|
+
import { LoopressCommand } from '../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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LoopressCommand } from '../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
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import got from 'got';
|
|
4
|
+
import { LoopressCommand } from '../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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ConsoleAuth {
|
|
2
|
+
email?: string;
|
|
3
|
+
savedAt: string;
|
|
4
|
+
token: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class AuthManager {
|
|
7
|
+
private readonly homeDir;
|
|
8
|
+
private static instance;
|
|
9
|
+
constructor(homeDir?: string);
|
|
10
|
+
static getInstance(): AuthManager;
|
|
11
|
+
clearAuth(): void;
|
|
12
|
+
getAuth(): ConsoleAuth | null;
|
|
13
|
+
getAuthFilePath(): string;
|
|
14
|
+
setAuth(auth: ConsoleAuth): void;
|
|
15
|
+
}
|
|
16
|
+
export declare const authManager: AuthManager;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export class AuthManager {
|
|
5
|
+
homeDir;
|
|
6
|
+
static instance;
|
|
7
|
+
constructor(homeDir = homedir()) {
|
|
8
|
+
this.homeDir = homeDir;
|
|
9
|
+
}
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!AuthManager.instance) {
|
|
12
|
+
AuthManager.instance = new AuthManager();
|
|
13
|
+
}
|
|
14
|
+
return AuthManager.instance;
|
|
15
|
+
}
|
|
16
|
+
clearAuth() {
|
|
17
|
+
const filePath = this.getAuthFilePath();
|
|
18
|
+
if (existsSync(filePath))
|
|
19
|
+
unlinkSync(filePath);
|
|
20
|
+
}
|
|
21
|
+
getAuth() {
|
|
22
|
+
const filePath = this.getAuthFilePath();
|
|
23
|
+
if (!existsSync(filePath))
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
getAuthFilePath() {
|
|
33
|
+
return join(this.homeDir, '.lps', 'auth.json');
|
|
34
|
+
}
|
|
35
|
+
setAuth(auth) {
|
|
36
|
+
const dir = join(this.homeDir, '.lps');
|
|
37
|
+
if (!existsSync(dir))
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
const filePath = this.getAuthFilePath();
|
|
40
|
+
const tmpPath = `${filePath}.tmp`;
|
|
41
|
+
writeFileSync(tmpPath, JSON.stringify(auth, null, 2));
|
|
42
|
+
renameSync(tmpPath, filePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const authManager = AuthManager.getInstance();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EnvironmentConfig, LoopressConfig, ProjectConfig } from './types.js';
|
|
2
|
+
export declare class ProjectConfigManager {
|
|
3
|
+
private readonly homeDir;
|
|
4
|
+
private static instance;
|
|
5
|
+
constructor(homeDir?: string);
|
|
6
|
+
static getInstance(): ProjectConfigManager;
|
|
7
|
+
ensureConfigDir(): void;
|
|
8
|
+
getConfigFilePath(): string;
|
|
9
|
+
getCurrentEnv(): EnvironmentConfig | null;
|
|
10
|
+
getCurrentProject(): null | ProjectConfig;
|
|
11
|
+
getEnvironment(projectName: string, envName: string): EnvironmentConfig | null;
|
|
12
|
+
getProject(name: string): null | ProjectConfig;
|
|
13
|
+
listEnvironments(projectName: string): Array<EnvironmentConfig & {
|
|
14
|
+
isCurrent: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
listProjects(): Array<ProjectConfig & {
|
|
17
|
+
isCurrent: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
readConfig(): LoopressConfig;
|
|
20
|
+
removeEnvironment(projectName: string, envName: string): void;
|
|
21
|
+
removeProject(name: string): void;
|
|
22
|
+
setCurrentEnv(projectName: string, envName: string): void;
|
|
23
|
+
setCurrentProject(name: string): void;
|
|
24
|
+
setEnvironment(projectName: string, envName: string, env: EnvironmentConfig): void;
|
|
25
|
+
setProject(name: string, project: ProjectConfig): void;
|
|
26
|
+
writeConfig(config: LoopressConfig): void;
|
|
27
|
+
}
|
|
28
|
+
export declare const configManager: ProjectConfigManager;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export class ProjectConfigManager {
|
|
5
|
+
homeDir;
|
|
6
|
+
static instance;
|
|
7
|
+
constructor(homeDir = homedir()) {
|
|
8
|
+
this.homeDir = homeDir;
|
|
9
|
+
}
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!ProjectConfigManager.instance) {
|
|
12
|
+
ProjectConfigManager.instance = new ProjectConfigManager();
|
|
13
|
+
}
|
|
14
|
+
return ProjectConfigManager.instance;
|
|
15
|
+
}
|
|
16
|
+
ensureConfigDir() {
|
|
17
|
+
const dir = join(this.homeDir, '.lps');
|
|
18
|
+
if (!existsSync(dir)) {
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getConfigFilePath() {
|
|
23
|
+
return join(this.homeDir, '.lps', 'config.json');
|
|
24
|
+
}
|
|
25
|
+
getCurrentEnv() {
|
|
26
|
+
const project = this.getCurrentProject();
|
|
27
|
+
if (!project || !project.currentEnv)
|
|
28
|
+
return null;
|
|
29
|
+
return project.environments[project.currentEnv] ?? null;
|
|
30
|
+
}
|
|
31
|
+
getCurrentProject() {
|
|
32
|
+
const config = this.readConfig();
|
|
33
|
+
if (!config.currentProject || !config.projects[config.currentProject])
|
|
34
|
+
return null;
|
|
35
|
+
return config.projects[config.currentProject];
|
|
36
|
+
}
|
|
37
|
+
getEnvironment(projectName, envName) {
|
|
38
|
+
const project = this.getProject(projectName);
|
|
39
|
+
if (!project)
|
|
40
|
+
return null;
|
|
41
|
+
return project.environments[envName] ?? null;
|
|
42
|
+
}
|
|
43
|
+
getProject(name) {
|
|
44
|
+
const config = this.readConfig();
|
|
45
|
+
return config.projects[name] ?? null;
|
|
46
|
+
}
|
|
47
|
+
listEnvironments(projectName) {
|
|
48
|
+
const project = this.getProject(projectName);
|
|
49
|
+
if (!project)
|
|
50
|
+
return [];
|
|
51
|
+
return Object.values(project.environments).map((env) => ({
|
|
52
|
+
...env,
|
|
53
|
+
isCurrent: env.name === project.currentEnv,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
listProjects() {
|
|
57
|
+
const config = this.readConfig();
|
|
58
|
+
return Object.values(config.projects).map((project) => ({
|
|
59
|
+
...project,
|
|
60
|
+
isCurrent: project.name === config.currentProject,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
readConfig() {
|
|
64
|
+
const filePath = this.getConfigFilePath();
|
|
65
|
+
if (!existsSync(filePath)) {
|
|
66
|
+
return { currentProject: null, projects: {} };
|
|
67
|
+
}
|
|
68
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
69
|
+
}
|
|
70
|
+
removeEnvironment(projectName, envName) {
|
|
71
|
+
const config = this.readConfig();
|
|
72
|
+
if (!config.projects[projectName])
|
|
73
|
+
return;
|
|
74
|
+
const project = config.projects[projectName];
|
|
75
|
+
delete project.environments[envName];
|
|
76
|
+
if (project.currentEnv === envName) {
|
|
77
|
+
const remaining = Object.keys(project.environments);
|
|
78
|
+
project.currentEnv = remaining.length > 0 ? remaining[0] : null;
|
|
79
|
+
}
|
|
80
|
+
this.writeConfig(config);
|
|
81
|
+
}
|
|
82
|
+
removeProject(name) {
|
|
83
|
+
const config = this.readConfig();
|
|
84
|
+
delete config.projects[name];
|
|
85
|
+
if (config.currentProject === name) {
|
|
86
|
+
const remaining = Object.keys(config.projects);
|
|
87
|
+
config.currentProject = remaining.length > 0 ? remaining[0] : null;
|
|
88
|
+
}
|
|
89
|
+
this.writeConfig(config);
|
|
90
|
+
}
|
|
91
|
+
setCurrentEnv(projectName, envName) {
|
|
92
|
+
const config = this.readConfig();
|
|
93
|
+
if (!config.projects[projectName])
|
|
94
|
+
return;
|
|
95
|
+
config.projects[projectName].currentEnv = envName;
|
|
96
|
+
this.writeConfig(config);
|
|
97
|
+
}
|
|
98
|
+
setCurrentProject(name) {
|
|
99
|
+
const config = this.readConfig();
|
|
100
|
+
config.currentProject = name;
|
|
101
|
+
this.writeConfig(config);
|
|
102
|
+
}
|
|
103
|
+
setEnvironment(projectName, envName, env) {
|
|
104
|
+
const config = this.readConfig();
|
|
105
|
+
if (!config.projects[projectName])
|
|
106
|
+
return;
|
|
107
|
+
const project = config.projects[projectName];
|
|
108
|
+
const isFirst = Object.keys(project.environments).length === 0;
|
|
109
|
+
project.environments[envName] = env;
|
|
110
|
+
if (isFirst)
|
|
111
|
+
project.currentEnv = envName;
|
|
112
|
+
this.writeConfig(config);
|
|
113
|
+
}
|
|
114
|
+
setProject(name, project) {
|
|
115
|
+
const config = this.readConfig();
|
|
116
|
+
const isFirst = Object.keys(config.projects).length === 0;
|
|
117
|
+
config.projects[name] = project;
|
|
118
|
+
if (isFirst)
|
|
119
|
+
config.currentProject = name;
|
|
120
|
+
this.writeConfig(config);
|
|
121
|
+
}
|
|
122
|
+
writeConfig(config) {
|
|
123
|
+
this.ensureConfigDir();
|
|
124
|
+
const filePath = this.getConfigFilePath();
|
|
125
|
+
const tmpPath = `${filePath}.tmp`;
|
|
126
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
127
|
+
renameSync(tmpPath, filePath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export const configManager = ProjectConfigManager.getInstance();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface EnvironmentConfig {
|
|
2
|
+
addedAt: string;
|
|
3
|
+
name: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProjectConfig {
|
|
8
|
+
addedAt: string;
|
|
9
|
+
currentEnv: null | string;
|
|
10
|
+
environments: Record<string, EnvironmentConfig>;
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LoopressConfig {
|
|
14
|
+
currentProject: null | string;
|
|
15
|
+
projects: Record<string, ProjectConfig>;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export async function readLocalConfig() {
|
|
4
|
+
const configPath = join(process.cwd(), 'loopress.config.js');
|
|
5
|
+
if (!existsSync(configPath))
|
|
6
|
+
return {};
|
|
7
|
+
try {
|
|
8
|
+
const mod = await import(configPath);
|
|
9
|
+
return mod.default ?? {};
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type PluginName = 'code-snippets' | 'wpcode';
|
|
2
|
+
export interface NormalizedSnippet {
|
|
3
|
+
active: boolean;
|
|
4
|
+
code: string;
|
|
5
|
+
description: string;
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
tags: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface SnippetPlugin {
|
|
11
|
+
endpoint(siteUrl: string): string;
|
|
12
|
+
fromRemote(data: Record<string, unknown>): NormalizedSnippet;
|
|
13
|
+
toPayload(name: string, code: string, path: string): Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export declare function getSnippetPlugin(name: PluginName): SnippetPlugin;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
class CodeSnippetsPlugin {
|
|
2
|
+
endpoint(siteUrl) {
|
|
3
|
+
return `${siteUrl}/wp-json/code-snippets/v1/snippets`;
|
|
4
|
+
}
|
|
5
|
+
fromRemote(data) {
|
|
6
|
+
return {
|
|
7
|
+
active: Boolean(data.active),
|
|
8
|
+
code: String(data.code ?? ''),
|
|
9
|
+
description: String(data.desc ?? ''),
|
|
10
|
+
id: Number(data.id),
|
|
11
|
+
name: String(data.name ?? ''),
|
|
12
|
+
tags: Array.isArray(data.tags) ? data.tags : [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
toPayload(name, code, path) {
|
|
16
|
+
return {
|
|
17
|
+
code,
|
|
18
|
+
desc: `Imported from ${path}`,
|
|
19
|
+
name,
|
|
20
|
+
tags: ['cli-import'],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class WPCodePlugin {
|
|
25
|
+
endpoint(siteUrl) {
|
|
26
|
+
return `${siteUrl}/wp-json/loopress/v1/wpcode/snippets`;
|
|
27
|
+
}
|
|
28
|
+
fromRemote(data) {
|
|
29
|
+
return {
|
|
30
|
+
active: Boolean(data.active),
|
|
31
|
+
code: String(data.code ?? ''),
|
|
32
|
+
description: String(data.note ?? ''),
|
|
33
|
+
id: Number(data.id),
|
|
34
|
+
name: String(data.title ?? ''),
|
|
35
|
+
tags: Array.isArray(data.tags) ? data.tags : [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
toPayload(name, code, path) {
|
|
39
|
+
return {
|
|
40
|
+
code,
|
|
41
|
+
note: `Imported from ${path}`,
|
|
42
|
+
tags: ['cli-import'],
|
|
43
|
+
title: name,
|
|
44
|
+
type: 'php',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function getSnippetPlugin(name) {
|
|
49
|
+
switch (name) {
|
|
50
|
+
case 'wpcode': {
|
|
51
|
+
return new WPCodePlugin();
|
|
52
|
+
}
|
|
53
|
+
case 'code-snippets':
|
|
54
|
+
default: {
|
|
55
|
+
return new CodeSnippetsPlugin();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|