@loopress/cli 0.6.0 → 0.8.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 +98 -117
- package/dist/commands/composer/pull.js +5 -15
- package/dist/commands/composer/push.js +8 -17
- package/dist/commands/init.js +15 -6
- package/dist/commands/login.js +2 -2
- package/dist/commands/logout.js +2 -2
- package/dist/commands/plugin/add.d.ts +0 -2
- package/dist/commands/plugin/add.js +11 -32
- package/dist/commands/plugin/pull.js +8 -19
- package/dist/commands/plugin/push.d.ts +1 -0
- package/dist/commands/plugin/push.js +23 -48
- package/dist/commands/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +36 -17
- package/dist/commands/project/list.js +4 -5
- package/dist/commands/project/remove.js +33 -14
- package/dist/commands/project/switch.d.ts +2 -0
- package/dist/commands/project/switch.js +28 -9
- package/dist/commands/snippet/list.d.ts +1 -1
- package/dist/commands/snippet/list.js +26 -41
- package/dist/commands/snippet/pull.d.ts +1 -1
- package/dist/commands/snippet/pull.js +45 -53
- package/dist/commands/snippet/push.d.ts +2 -2
- package/dist/commands/snippet/push.js +112 -68
- package/dist/commands/{project/remove-env.d.ts → status.d.ts} +3 -1
- package/dist/commands/status.js +66 -0
- package/dist/config/auth.manager.d.ts +0 -2
- package/dist/config/auth.manager.js +5 -25
- package/dist/config/json-file.d.ts +2 -0
- package/dist/config/json-file.js +21 -0
- package/dist/config/project-config.manager.d.ts +18 -13
- package/dist/config/project-config.manager.js +90 -59
- package/dist/lib/base.d.ts +14 -4
- package/dist/lib/base.js +69 -29
- package/dist/lib/push-command.d.ts +0 -1
- package/dist/lib/push-command.js +0 -1
- package/dist/lib/wp-client.d.ts +15 -0
- package/dist/lib/wp-client.js +53 -0
- package/dist/{config/types.d.ts → types/config.d.ts} +5 -2
- package/dist/types/snippet.d.ts +8 -0
- package/dist/utils/loopress-config.js +5 -2
- package/dist/utils/snippet-plugin-flag.d.ts +3 -0
- package/dist/utils/snippet-plugin-flag.js +8 -0
- package/dist/utils/snippet-plugin.d.ts +24 -2
- package/dist/utils/snippet-plugin.js +170 -14
- package/oclif.manifest.json +122 -108
- package/package.json +21 -4
- package/dist/commands/project/remove-env.js +0 -33
- package/dist/commands/project/switch-env.d.ts +0 -6
- package/dist/commands/project/switch-env.js +0 -33
- package/dist/types/menu.d.ts +0 -7
- package/dist/types/menu.js +0 -1
- /package/dist/{config/types.js → types/config.js} +0 -0
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { join } from 'node:path';
|
|
5
|
+
import { readJsonFile, writeJsonFileAtomic } from './json-file.js';
|
|
4
6
|
export class ProjectConfigManager {
|
|
5
7
|
homeDir;
|
|
6
|
-
static instance;
|
|
7
8
|
constructor(homeDir = homedir()) {
|
|
8
9
|
this.homeDir = homeDir;
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ProjectConfigManager.instance = new ProjectConfigManager();
|
|
13
|
-
}
|
|
14
|
-
return ProjectConfigManager.instance;
|
|
11
|
+
createProjectId() {
|
|
12
|
+
return randomUUID();
|
|
15
13
|
}
|
|
16
14
|
ensureConfigDir() {
|
|
17
15
|
const dir = join(this.homeDir, '.loopress');
|
|
@@ -23,108 +21,141 @@ export class ProjectConfigManager {
|
|
|
23
21
|
return join(this.homeDir, '.loopress', 'config.json');
|
|
24
22
|
}
|
|
25
23
|
getCurrentEnv() {
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
24
|
+
const config = this.readConfig();
|
|
25
|
+
if (!config.currentProject)
|
|
28
26
|
return null;
|
|
29
|
-
|
|
27
|
+
const project = config.projects[config.currentProject.id];
|
|
28
|
+
if (!project)
|
|
29
|
+
return null;
|
|
30
|
+
return project.environments[config.currentProject.env] ?? null;
|
|
30
31
|
}
|
|
31
32
|
getCurrentProject() {
|
|
32
33
|
const config = this.readConfig();
|
|
33
|
-
if (!config.currentProject
|
|
34
|
+
if (!config.currentProject)
|
|
34
35
|
return null;
|
|
35
|
-
|
|
36
|
+
const project = config.projects[config.currentProject.id];
|
|
37
|
+
if (!project)
|
|
38
|
+
return null;
|
|
39
|
+
return { ...project, id: config.currentProject.id };
|
|
36
40
|
}
|
|
37
|
-
getEnvironment(
|
|
38
|
-
const project = this.getProject(
|
|
41
|
+
getEnvironment(projectId, envName) {
|
|
42
|
+
const project = this.getProject(projectId);
|
|
39
43
|
if (!project)
|
|
40
44
|
return null;
|
|
41
45
|
return project.environments[envName] ?? null;
|
|
42
46
|
}
|
|
43
|
-
getProject(
|
|
47
|
+
getProject(id) {
|
|
44
48
|
const config = this.readConfig();
|
|
45
|
-
return config.projects[
|
|
49
|
+
return config.projects[id] ?? null;
|
|
46
50
|
}
|
|
47
|
-
listEnvironments(
|
|
48
|
-
const
|
|
51
|
+
listEnvironments(projectId) {
|
|
52
|
+
const config = this.readConfig();
|
|
53
|
+
const project = config.projects[projectId];
|
|
49
54
|
if (!project)
|
|
50
55
|
return [];
|
|
51
56
|
return Object.values(project.environments).map((env) => ({
|
|
52
57
|
...env,
|
|
53
|
-
isCurrent:
|
|
58
|
+
isCurrent: config.currentProject?.id === projectId && config.currentProject.env === env.name,
|
|
54
59
|
}));
|
|
55
60
|
}
|
|
56
61
|
listProjects() {
|
|
57
62
|
const config = this.readConfig();
|
|
58
|
-
return Object.
|
|
63
|
+
return Object.entries(config.projects).map(([id, project]) => ({
|
|
59
64
|
...project,
|
|
60
|
-
|
|
65
|
+
id,
|
|
66
|
+
isCurrent: config.currentProject?.id === id,
|
|
61
67
|
}));
|
|
62
68
|
}
|
|
63
69
|
readConfig() {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
70
|
+
const parsed = readJsonFile(this.getConfigFilePath());
|
|
71
|
+
if (parsed === null) {
|
|
66
72
|
return { currentProject: null, projects: {} };
|
|
67
73
|
}
|
|
68
|
-
return
|
|
74
|
+
return this.sanitizeConfig(parsed);
|
|
69
75
|
}
|
|
70
|
-
removeEnvironment(
|
|
76
|
+
removeEnvironment(projectId, envName) {
|
|
71
77
|
const config = this.readConfig();
|
|
72
|
-
|
|
78
|
+
const project = config.projects[projectId];
|
|
79
|
+
if (!project)
|
|
73
80
|
return;
|
|
74
|
-
const project = config.projects[projectName];
|
|
75
81
|
delete project.environments[envName];
|
|
76
|
-
if (
|
|
82
|
+
if (config.currentProject?.id === projectId && config.currentProject.env === envName) {
|
|
77
83
|
const remaining = Object.keys(project.environments);
|
|
78
|
-
|
|
84
|
+
config.currentProject = remaining.length > 0 ? { env: remaining[0], id: projectId } : null;
|
|
79
85
|
}
|
|
80
86
|
this.writeConfig(config);
|
|
81
87
|
}
|
|
82
|
-
removeProject(
|
|
88
|
+
removeProject(id) {
|
|
83
89
|
const config = this.readConfig();
|
|
84
|
-
delete config.projects[
|
|
85
|
-
if (config.currentProject ===
|
|
86
|
-
const
|
|
87
|
-
|
|
90
|
+
delete config.projects[id];
|
|
91
|
+
if (config.currentProject?.id === id) {
|
|
92
|
+
const [nextId] = Object.keys(config.projects);
|
|
93
|
+
const nextProject = nextId ? config.projects[nextId] : undefined;
|
|
94
|
+
const [nextEnv] = nextProject ? Object.keys(nextProject.environments) : [];
|
|
95
|
+
config.currentProject = nextId && nextEnv ? { env: nextEnv, id: nextId } : null;
|
|
88
96
|
}
|
|
89
97
|
this.writeConfig(config);
|
|
90
98
|
}
|
|
91
|
-
|
|
99
|
+
setCurrent(projectId, envName) {
|
|
92
100
|
const config = this.readConfig();
|
|
93
|
-
if (!config.projects[
|
|
101
|
+
if (!config.projects[projectId])
|
|
94
102
|
return;
|
|
95
|
-
config.
|
|
96
|
-
this.writeConfig(config);
|
|
97
|
-
}
|
|
98
|
-
setCurrentProject(name) {
|
|
99
|
-
const config = this.readConfig();
|
|
100
|
-
config.currentProject = name;
|
|
103
|
+
config.currentProject = { env: envName, id: projectId };
|
|
101
104
|
this.writeConfig(config);
|
|
102
105
|
}
|
|
103
|
-
setEnvironment(
|
|
106
|
+
setEnvironment(projectId, envName, env) {
|
|
104
107
|
const config = this.readConfig();
|
|
105
|
-
|
|
108
|
+
const project = config.projects[projectId];
|
|
109
|
+
if (!project)
|
|
106
110
|
return;
|
|
107
|
-
const project = config.projects[projectName];
|
|
108
|
-
const isFirst = Object.keys(project.environments).length === 0;
|
|
109
111
|
project.environments[envName] = env;
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
+
if (!config.currentProject)
|
|
113
|
+
config.currentProject = { env: envName, id: projectId };
|
|
112
114
|
this.writeConfig(config);
|
|
113
115
|
}
|
|
114
|
-
setProject(
|
|
116
|
+
setProject(id, project) {
|
|
115
117
|
const config = this.readConfig();
|
|
116
|
-
|
|
117
|
-
config.
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
config.projects[id] = project;
|
|
119
|
+
if (!config.currentProject) {
|
|
120
|
+
const [firstEnv] = Object.keys(project.environments);
|
|
121
|
+
if (firstEnv)
|
|
122
|
+
config.currentProject = { env: firstEnv, id };
|
|
123
|
+
}
|
|
120
124
|
this.writeConfig(config);
|
|
121
125
|
}
|
|
122
126
|
writeConfig(config) {
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
writeJsonFileAtomic(this.getConfigFilePath(), config);
|
|
128
|
+
}
|
|
129
|
+
isProjectConfig(value) {
|
|
130
|
+
if (typeof value !== 'object' || value === null)
|
|
131
|
+
return false;
|
|
132
|
+
const candidate = value;
|
|
133
|
+
return typeof candidate.name === 'string' && typeof candidate.environments === 'object' && candidate.environments !== null;
|
|
134
|
+
}
|
|
135
|
+
sanitizeConfig(value) {
|
|
136
|
+
if (typeof value !== 'object' || value === null)
|
|
137
|
+
return { currentProject: null, projects: {} };
|
|
138
|
+
const candidate = value;
|
|
139
|
+
return {
|
|
140
|
+
currentProject: this.sanitizeCurrentProject(candidate.currentProject),
|
|
141
|
+
projects: this.sanitizeProjects(candidate.projects),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
sanitizeCurrentProject(value) {
|
|
145
|
+
if (value === null || typeof value !== 'object')
|
|
146
|
+
return null;
|
|
147
|
+
const pointer = value;
|
|
148
|
+
return typeof pointer.id === 'string' && typeof pointer.env === 'string' ? { env: pointer.env, id: pointer.id } : null;
|
|
149
|
+
}
|
|
150
|
+
sanitizeProjects(value) {
|
|
151
|
+
if (typeof value !== 'object' || value === null)
|
|
152
|
+
return {};
|
|
153
|
+
const projects = {};
|
|
154
|
+
for (const [id, project] of Object.entries(value)) {
|
|
155
|
+
if (this.isProjectConfig(project))
|
|
156
|
+
projects[id] = project;
|
|
157
|
+
}
|
|
158
|
+
return projects;
|
|
128
159
|
}
|
|
129
160
|
}
|
|
130
|
-
export const configManager = ProjectConfigManager
|
|
161
|
+
export const configManager = new ProjectConfigManager();
|
package/dist/lib/base.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { EnvironmentConfig } from '../config
|
|
2
|
+
import { EnvironmentConfig } from '../types/config.js';
|
|
3
|
+
import { LoopressLocalConfig } from '../utils/loopress-config.js';
|
|
4
|
+
import { WpClient } from './wp-client.js';
|
|
3
5
|
export declare abstract class LoopressCommand extends Command {
|
|
4
6
|
static baseFlags: {
|
|
5
7
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
8
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
9
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
};
|
|
11
|
+
static dryRunFlag: {
|
|
12
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protected dryRun: boolean;
|
|
15
|
+
protected localConfig: LoopressLocalConfig;
|
|
9
16
|
protected siteConfig: EnvironmentConfig;
|
|
10
|
-
|
|
17
|
+
private wpClient?;
|
|
18
|
+
protected get rootDir(): string;
|
|
19
|
+
protected get wp(): WpClient;
|
|
11
20
|
init(): Promise<void>;
|
|
12
|
-
protected resolveSnippetPlugin(flag?: string):
|
|
13
|
-
protected resolveSnippetsPath(override?: string):
|
|
21
|
+
protected resolveSnippetPlugin(flag?: string): 'code-snippets' | 'wpcode';
|
|
22
|
+
protected resolveSnippetsPath(override?: string): string;
|
|
23
|
+
private resolveEnvironment;
|
|
14
24
|
}
|
package/dist/lib/base.js
CHANGED
|
@@ -2,60 +2,100 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { configManager } from '../config/project-config.manager.js';
|
|
4
4
|
import { readLocalConfig } from '../utils/loopress-config.js';
|
|
5
|
+
import { WpClient } from './wp-client.js';
|
|
5
6
|
export class LoopressCommand extends Command {
|
|
6
7
|
static baseFlags = {
|
|
7
8
|
password: Flags.string({
|
|
8
|
-
description: 'WordPress application password (
|
|
9
|
+
description: 'WordPress application password (overrides project config, requires --user)',
|
|
9
10
|
helpGroup: 'GLOBAL',
|
|
10
11
|
}),
|
|
11
12
|
url: Flags.string({
|
|
12
|
-
description: 'WordPress URL (
|
|
13
|
+
description: 'WordPress URL (overrides project config)',
|
|
13
14
|
helpGroup: 'GLOBAL',
|
|
14
15
|
}),
|
|
15
16
|
user: Flags.string({
|
|
16
|
-
description: 'WordPress username (
|
|
17
|
+
description: 'WordPress username (overrides project config, requires --password)',
|
|
17
18
|
helpGroup: 'GLOBAL',
|
|
18
19
|
}),
|
|
19
20
|
};
|
|
21
|
+
static dryRunFlag = {
|
|
22
|
+
'dry-run': Flags.boolean({ char: 'd', description: 'Show what would change without making changes' }),
|
|
23
|
+
};
|
|
24
|
+
dryRun = false;
|
|
25
|
+
localConfig = {};
|
|
20
26
|
siteConfig;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
wpClient;
|
|
28
|
+
get rootDir() {
|
|
29
|
+
return this.localConfig.rootDir ?? '.';
|
|
30
|
+
}
|
|
31
|
+
get wp() {
|
|
32
|
+
if (!this.wpClient) {
|
|
33
|
+
const { token, url } = this.siteConfig;
|
|
34
|
+
if (!token) {
|
|
35
|
+
this.error(`No credentials configured for ${url}. Run \`lps project config\` to add them.`);
|
|
36
|
+
}
|
|
37
|
+
this.wpClient = new WpClient(url, token);
|
|
25
38
|
}
|
|
26
|
-
this.
|
|
39
|
+
return this.wpClient;
|
|
27
40
|
}
|
|
28
41
|
async init() {
|
|
29
42
|
await super.init();
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.siteConfig = project.environments[project.currentEnv];
|
|
40
|
-
return;
|
|
43
|
+
const { flags } = (await this.parse({
|
|
44
|
+
args: this.ctor.args,
|
|
45
|
+
flags: this.ctor.flags,
|
|
46
|
+
strict: this.ctor.strict,
|
|
47
|
+
}));
|
|
48
|
+
this.dryRun = Boolean(flags['dry-run']);
|
|
49
|
+
this.localConfig = await readLocalConfig();
|
|
50
|
+
if (Boolean(flags.user) !== Boolean(flags.password)) {
|
|
51
|
+
this.error('--user and --password must be provided together.');
|
|
41
52
|
}
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
this.siteConfig =
|
|
53
|
+
const flagToken = flags.user && flags.password ? `${flags.user}:${flags.password}` : undefined;
|
|
54
|
+
if (flags.url) {
|
|
55
|
+
this.siteConfig = {
|
|
56
|
+
addedAt: new Date().toISOString(),
|
|
57
|
+
name: 'cli-flags',
|
|
58
|
+
token: flagToken,
|
|
59
|
+
url: flags.url.replace(/\/+$/, ''),
|
|
60
|
+
};
|
|
45
61
|
return;
|
|
46
62
|
}
|
|
47
|
-
|
|
63
|
+
const env = this.resolveEnvironment();
|
|
64
|
+
this.siteConfig = flagToken ? { ...env, token: flagToken } : env;
|
|
48
65
|
}
|
|
49
|
-
|
|
66
|
+
resolveSnippetPlugin(flag) {
|
|
50
67
|
if (flag)
|
|
51
68
|
return flag;
|
|
52
|
-
|
|
53
|
-
return config.snippetPlugin ?? 'wpcode';
|
|
69
|
+
return this.localConfig.snippetPlugin ?? 'wpcode';
|
|
54
70
|
}
|
|
55
|
-
|
|
71
|
+
resolveSnippetsPath(override) {
|
|
56
72
|
if (override)
|
|
57
73
|
return override;
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
return join(this.rootDir, this.localConfig.snippetsDir ?? 'snippets');
|
|
75
|
+
}
|
|
76
|
+
resolveEnvironment() {
|
|
77
|
+
if (this.localConfig.projectId) {
|
|
78
|
+
const project = configManager.getProject(this.localConfig.projectId);
|
|
79
|
+
if (!project) {
|
|
80
|
+
this.error(`Project "${this.localConfig.projectId}" (from loopress.json) not found. Run \`lps project config\` to configure it.`);
|
|
81
|
+
}
|
|
82
|
+
const envNames = Object.keys(project.environments);
|
|
83
|
+
if (envNames.length === 0) {
|
|
84
|
+
this.error(`Project "${project.name}" has no environments configured. Run \`lps project config\` to add one.`);
|
|
85
|
+
}
|
|
86
|
+
if (envNames.length === 1) {
|
|
87
|
+
return project.environments[envNames[0]];
|
|
88
|
+
}
|
|
89
|
+
const current = configManager.getCurrentProject();
|
|
90
|
+
const currentEnv = current?.id === this.localConfig.projectId ? configManager.getCurrentEnv() : null;
|
|
91
|
+
if (!currentEnv) {
|
|
92
|
+
this.error(`Project "${project.name}" has multiple environments. Run \`lps project switch\` to pick one.`);
|
|
93
|
+
}
|
|
94
|
+
return currentEnv;
|
|
95
|
+
}
|
|
96
|
+
const env = configManager.getCurrentEnv();
|
|
97
|
+
if (env)
|
|
98
|
+
return env;
|
|
99
|
+
this.error('No environment configured. Run `lps project config` first.');
|
|
60
100
|
}
|
|
61
101
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { LoopressCommand } from './base.js';
|
|
2
2
|
export declare abstract class PushCommand extends LoopressCommand {
|
|
3
|
-
protected dryRun: boolean;
|
|
4
3
|
catch(err: Error): Promise<void>;
|
|
5
4
|
protected recordDeployment(status: 'failure' | 'success'): Promise<void>;
|
|
6
5
|
protected recordSuccess(): Promise<void>;
|
package/dist/lib/push-command.js
CHANGED
|
@@ -3,7 +3,6 @@ import { authManager } from '../config/auth.manager.js';
|
|
|
3
3
|
import { LoopressCommand } from './base.js';
|
|
4
4
|
const API_URL = process.env.LPS_API_URL ?? 'https://api.loopress.dev';
|
|
5
5
|
export class PushCommand extends LoopressCommand {
|
|
6
|
-
dryRun = false;
|
|
7
6
|
async catch(err) {
|
|
8
7
|
if (!this.dryRun && this.siteConfig) {
|
|
9
8
|
await this.recordDeployment('failure');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const REQUEST_TIMEOUT_MS = 30000;
|
|
2
|
+
/**
|
|
3
|
+
* HTTP client for a WordPress site's REST API.
|
|
4
|
+
* Paths are relative to `<site>/wp-json/`, e.g. `loopress/v1/plugins`.
|
|
5
|
+
*/
|
|
6
|
+
export declare class WpClient {
|
|
7
|
+
private readonly siteUrl;
|
|
8
|
+
private readonly client;
|
|
9
|
+
constructor(siteUrl: string, token: string);
|
|
10
|
+
get<T>(path: string): Promise<T>;
|
|
11
|
+
post<T = unknown>(path: string, json?: Record<string, unknown>): Promise<T>;
|
|
12
|
+
put<T = unknown>(path: string, json?: Record<string, unknown>): Promise<T>;
|
|
13
|
+
private request;
|
|
14
|
+
}
|
|
15
|
+
export declare function formatWpError(error: unknown, url: string): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
3
|
+
/**
|
|
4
|
+
* HTTP client for a WordPress site's REST API.
|
|
5
|
+
* Paths are relative to `<site>/wp-json/`, e.g. `loopress/v1/plugins`.
|
|
6
|
+
*/
|
|
7
|
+
export class WpClient {
|
|
8
|
+
siteUrl;
|
|
9
|
+
client;
|
|
10
|
+
constructor(siteUrl, token) {
|
|
11
|
+
this.siteUrl = siteUrl;
|
|
12
|
+
this.client = got.extend({
|
|
13
|
+
headers: { Authorization: `Basic ${Buffer.from(token).toString('base64')}` },
|
|
14
|
+
prefixUrl: `${siteUrl}/wp-json`,
|
|
15
|
+
timeout: { request: REQUEST_TIMEOUT_MS },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async get(path) {
|
|
19
|
+
return this.request('get', path);
|
|
20
|
+
}
|
|
21
|
+
async post(path, json) {
|
|
22
|
+
return this.request('post', path, json);
|
|
23
|
+
}
|
|
24
|
+
async put(path, json) {
|
|
25
|
+
return this.request('put', path, json);
|
|
26
|
+
}
|
|
27
|
+
async request(method, path, json) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await this.client(path, { json, method });
|
|
30
|
+
return (response.body ? JSON.parse(response.body) : undefined);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(formatWpError(error, `${this.siteUrl}/wp-json/${path}`), { cause: error });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function formatWpError(error, url) {
|
|
38
|
+
const err = error;
|
|
39
|
+
const status = err.response?.statusCode;
|
|
40
|
+
if (status === 401 || status === 403) {
|
|
41
|
+
return `Authentication failed (${status}) on ${url}. Check your credentials with \`lps project config\`.`;
|
|
42
|
+
}
|
|
43
|
+
if (status === 404) {
|
|
44
|
+
return `Endpoint not found (404) on ${url}. Is the required plugin installed and up to date on the site?`;
|
|
45
|
+
}
|
|
46
|
+
if (status !== undefined) {
|
|
47
|
+
return `Request failed (${status}) on ${url}.`;
|
|
48
|
+
}
|
|
49
|
+
if (err.name === 'TimeoutError') {
|
|
50
|
+
return `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s on ${url}. Is the site reachable?`;
|
|
51
|
+
}
|
|
52
|
+
return `Request to ${url} failed: ${err.message ?? String(error)}`;
|
|
53
|
+
}
|
|
@@ -6,11 +6,14 @@ export interface EnvironmentConfig {
|
|
|
6
6
|
}
|
|
7
7
|
export interface ProjectConfig {
|
|
8
8
|
addedAt: string;
|
|
9
|
-
currentEnv: null | string;
|
|
10
9
|
environments: Record<string, EnvironmentConfig>;
|
|
11
10
|
name: string;
|
|
12
11
|
}
|
|
12
|
+
export interface CurrentProjectPointer {
|
|
13
|
+
env: string;
|
|
14
|
+
id: string;
|
|
15
|
+
}
|
|
13
16
|
export interface LoopressConfig {
|
|
14
|
-
currentProject:
|
|
17
|
+
currentProject: CurrentProjectPointer | null;
|
|
15
18
|
projects: Record<string, ProjectConfig>;
|
|
16
19
|
}
|
package/dist/types/snippet.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { SnippetInsertMethod, SnippetLocation, SnippetType } from '../utils/snippet-plugin.js';
|
|
1
2
|
export interface Snippet {
|
|
3
|
+
active: boolean;
|
|
2
4
|
code: string;
|
|
3
5
|
id?: number;
|
|
6
|
+
insertMethod: SnippetInsertMethod;
|
|
7
|
+
location: SnippetLocation;
|
|
4
8
|
name: string;
|
|
5
9
|
path: string;
|
|
10
|
+
priority: number;
|
|
11
|
+
shortcodeAttributes: string[];
|
|
12
|
+
tags: string[];
|
|
13
|
+
type: SnippetType;
|
|
6
14
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
// Only a missing file is treated as "no config" (returns {}). A file that exists but
|
|
5
|
+
// fails to read or parse throws, so callers don't silently fall back to the global
|
|
6
|
+
// current environment when loopress.json is actually broken.
|
|
4
7
|
export async function readLocalConfig() {
|
|
5
8
|
const configPath = join(process.cwd(), 'loopress.json');
|
|
6
9
|
if (!existsSync(configPath))
|
|
7
10
|
return {};
|
|
11
|
+
const content = await readFile(configPath, 'utf8');
|
|
8
12
|
try {
|
|
9
|
-
const content = await readFile(configPath, 'utf8');
|
|
10
13
|
return JSON.parse(content);
|
|
11
14
|
}
|
|
12
15
|
catch {
|
|
13
|
-
|
|
16
|
+
throw new Error('loopress.json is not valid JSON. Fix or delete it, then run `lps init` again.');
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
export async function writeLocalConfig(config) {
|
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
export type PluginName = 'code-snippets' | 'wpcode';
|
|
2
2
|
export type SnippetType = 'css' | 'html' | 'js' | 'php' | 'text';
|
|
3
|
+
export type SnippetInsertMethod = 'auto' | 'shortcode';
|
|
4
|
+
export type SnippetLocation = 'admin' | 'body' | 'everywhere' | 'footer' | 'frontend' | 'header' | 'once';
|
|
3
5
|
export interface NormalizedSnippet {
|
|
4
6
|
active: boolean;
|
|
5
7
|
code: string;
|
|
6
8
|
description: string;
|
|
7
9
|
id: number;
|
|
10
|
+
insertMethod: SnippetInsertMethod;
|
|
11
|
+
location: SnippetLocation;
|
|
8
12
|
name: string;
|
|
13
|
+
priority: number;
|
|
14
|
+
shortcodeAttributes: string[];
|
|
15
|
+
tags: string[];
|
|
16
|
+
type: SnippetType;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseType(raw: unknown): null | SnippetType;
|
|
19
|
+
export declare function parseLocation(raw: unknown): null | SnippetLocation;
|
|
20
|
+
export declare function parseInsertMethod(raw: unknown): null | SnippetInsertMethod;
|
|
21
|
+
export declare function defaultLocationForType(type: SnippetType): SnippetLocation;
|
|
22
|
+
export interface SnippetPayloadInput {
|
|
23
|
+
active: boolean;
|
|
24
|
+
code: string;
|
|
25
|
+
insertMethod: SnippetInsertMethod;
|
|
26
|
+
location: SnippetLocation;
|
|
27
|
+
name: string;
|
|
28
|
+
path: string;
|
|
29
|
+
priority: number;
|
|
30
|
+
shortcodeAttributes: string[];
|
|
9
31
|
tags: string[];
|
|
10
32
|
type: SnippetType;
|
|
11
33
|
}
|
|
12
34
|
export interface SnippetPlugin {
|
|
13
|
-
|
|
35
|
+
endpointPath(): string;
|
|
14
36
|
fromRemote(data: Record<string, unknown>): NormalizedSnippet;
|
|
15
|
-
toPayload(
|
|
37
|
+
toPayload(snippet: SnippetPayloadInput): Record<string, unknown>;
|
|
16
38
|
}
|
|
17
39
|
export declare function getSnippetPlugin(name: PluginName): SnippetPlugin;
|