@loopress/cli 0.28.0 → 0.30.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 +1460 -337
- package/bin/dev.js +0 -0
- package/dist/commands/acf/pull.d.ts +9 -1
- package/dist/commands/acf/pull.js +2 -0
- package/dist/commands/acf/push.d.ts +9 -1
- package/dist/commands/acf/push.js +3 -1
- package/dist/commands/api/publish.js +1 -1
- package/dist/commands/app/push.js +1 -1
- package/dist/commands/composer/push.js +3 -3
- package/dist/commands/cpt/diff.js +5 -0
- package/dist/commands/cpt/list.d.ts +2 -0
- package/dist/commands/cpt/list.js +4 -0
- package/dist/commands/cpt/pull.d.ts +2 -0
- package/dist/commands/cpt/pull.js +4 -0
- package/dist/commands/cpt/push.d.ts +2 -0
- package/dist/commands/cpt/push.js +4 -0
- package/dist/commands/cpt/rm.d.ts +2 -0
- package/dist/commands/cpt/rm.js +3 -0
- package/dist/commands/cpt/rollback.js +5 -0
- package/dist/commands/dev.js +14 -4
- package/dist/commands/diff.js +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +30 -2
- package/dist/commands/form/pull.d.ts +9 -1
- package/dist/commands/form/pull.js +3 -1
- package/dist/commands/form/push.d.ts +7 -1
- package/dist/commands/form/push.js +3 -1
- package/dist/commands/init.js +6 -38
- package/dist/commands/menu/pull.d.ts +7 -1
- package/dist/commands/menu/pull.js +2 -0
- package/dist/commands/menu/push.d.ts +7 -1
- package/dist/commands/menu/push.js +3 -1
- package/dist/commands/meta/diff.d.ts +2 -0
- package/dist/commands/meta/diff.js +5 -0
- package/dist/commands/meta/list.d.ts +19 -0
- package/dist/commands/meta/list.js +21 -0
- package/dist/commands/meta/pull.d.ts +2 -0
- package/dist/commands/meta/pull.js +4 -0
- package/dist/commands/meta/push.d.ts +2 -0
- package/dist/commands/meta/push.js +4 -0
- package/dist/commands/meta/rm.d.ts +2 -0
- package/dist/commands/meta/rm.js +3 -0
- package/dist/commands/meta/rollback.d.ts +2 -0
- package/dist/commands/meta/rollback.js +5 -0
- package/dist/commands/option/list.js +2 -8
- package/dist/commands/option/pull.js +4 -4
- package/dist/commands/option/push.js +2 -0
- package/dist/commands/page/push.js +3 -0
- package/dist/commands/pattern/diff.d.ts +2 -0
- package/dist/commands/pattern/diff.js +5 -0
- package/dist/commands/pattern/list.d.ts +2 -0
- package/dist/commands/pattern/list.js +3 -0
- package/dist/commands/pattern/pull.d.ts +2 -0
- package/dist/commands/pattern/pull.js +4 -0
- package/dist/commands/pattern/push.d.ts +2 -0
- package/dist/commands/pattern/push.js +4 -0
- package/dist/commands/pattern/rm.d.ts +2 -0
- package/dist/commands/pattern/rm.js +3 -0
- package/dist/commands/pattern/rollback.d.ts +2 -0
- package/dist/commands/pattern/rollback.js +5 -0
- package/dist/commands/plugin/audit.js +2 -1
- package/dist/commands/plugin/pull.d.ts +4 -0
- package/dist/commands/plugin/pull.js +56 -10
- package/dist/commands/plugin/push.js +5 -4
- package/dist/commands/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +20 -1
- package/dist/commands/project/list.d.ts +1 -1
- package/dist/commands/project/list.js +6 -6
- package/dist/commands/project/pull.js +11 -8
- package/dist/commands/project/push.js +23 -18
- package/dist/commands/project/remove.js +1 -1
- package/dist/commands/project/switch.d.ts +0 -1
- package/dist/commands/project/switch.js +16 -36
- package/dist/commands/pull.js +7 -2
- package/dist/commands/push.js +14 -6
- package/dist/commands/role/diff.d.ts +2 -0
- package/dist/commands/role/diff.js +5 -0
- package/dist/commands/role/list.d.ts +2 -0
- package/dist/commands/role/list.js +4 -0
- package/dist/commands/role/pull.d.ts +2 -0
- package/dist/commands/role/pull.js +4 -0
- package/dist/commands/role/push.d.ts +2 -0
- package/dist/commands/role/push.js +4 -0
- package/dist/commands/role/rm.d.ts +2 -0
- package/dist/commands/role/rm.js +3 -0
- package/dist/commands/role/rollback.d.ts +2 -0
- package/dist/commands/role/rollback.js +5 -0
- package/dist/commands/snippet/publish.js +1 -1
- package/dist/commands/snippet/pull.js +1 -1
- package/dist/commands/snippet/push.js +1 -1
- package/dist/commands/status.d.ts +3 -3
- package/dist/commands/status.js +21 -53
- package/dist/commands/taxonomy/diff.d.ts +2 -0
- package/dist/commands/taxonomy/diff.js +5 -0
- package/dist/commands/taxonomy/list.d.ts +2 -0
- package/dist/commands/taxonomy/list.js +5 -0
- package/dist/commands/taxonomy/pull.d.ts +2 -0
- package/dist/commands/taxonomy/pull.js +4 -0
- package/dist/commands/taxonomy/push.d.ts +2 -0
- package/dist/commands/taxonomy/push.js +4 -0
- package/dist/commands/taxonomy/rm.d.ts +2 -0
- package/dist/commands/taxonomy/rm.js +3 -0
- package/dist/commands/taxonomy/rollback.d.ts +2 -0
- package/dist/commands/taxonomy/rollback.js +5 -0
- package/dist/commands/theme/pull.d.ts +6 -10
- package/dist/commands/theme/pull.js +30 -32
- package/dist/commands/theme/push.d.ts +8 -9
- package/dist/commands/theme/push.js +54 -111
- package/dist/commands/theme/style/pull.d.ts +7 -1
- package/dist/commands/theme/style/pull.js +3 -1
- package/dist/commands/theme/style/push.d.ts +8 -1
- package/dist/commands/theme/style/push.js +4 -1
- package/dist/commands/{template → theme/template}/diff.d.ts +1 -1
- package/dist/commands/{template → theme/template}/diff.js +3 -3
- package/dist/commands/{template → theme/template}/list.d.ts +2 -2
- package/dist/commands/{template → theme/template}/list.js +5 -5
- package/dist/commands/{template → theme/template}/push.d.ts +3 -1
- package/dist/commands/theme/template/push.js +65 -0
- package/dist/commands/theme/version/pull.d.ts +23 -0
- package/dist/commands/theme/version/pull.js +43 -0
- package/dist/commands/theme/version/push.d.ts +22 -0
- package/dist/commands/theme/version/push.js +123 -0
- package/dist/config/project-config.manager.d.ts +3 -3
- package/dist/config/project-config.manager.js +22 -31
- package/dist/hooks/finally.d.ts +2 -1
- package/dist/hooks/finally.js +38 -7
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/app-build.js +3 -1
- package/dist/lib/app-manifest.js +2 -2
- package/dist/lib/base.d.ts +2 -2
- package/dist/lib/base.js +33 -38
- package/dist/lib/declared-type-command.d.ts +35 -0
- package/dist/lib/declared-type-command.js +192 -0
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +7 -3
- package/dist/lib/diff-state.js +1 -0
- package/dist/lib/github-release.js +4 -1
- package/dist/lib/local-callback-server.d.ts +12 -6
- package/dist/lib/local-callback-server.js +24 -23
- package/dist/lib/php-files-command.js +3 -1
- package/dist/lib/push-command.d.ts +6 -0
- package/dist/lib/push-command.js +61 -18
- package/dist/lib/put-or-create.js +3 -4
- package/dist/lib/resource-state.js +40 -86
- package/dist/lib/rollback-materialize.js +13 -24
- package/dist/lib/snapshot-store.js +2 -2
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +72 -0
- package/dist/lib/temp-admin.d.ts +10 -1
- package/dist/lib/temp-admin.js +27 -2
- package/dist/lib/validate-local.js +31 -9
- package/dist/lib/wp-authorize-flow.d.ts +10 -17
- package/dist/lib/wp-authorize-flow.js +24 -28
- package/dist/lib/wp-client.d.ts +1 -0
- package/dist/lib/wp-client.js +7 -1
- package/dist/lib/wp-site-diagnostic.js +39 -1
- package/dist/types/global-config.generated.d.ts +1 -5
- package/dist/types/project-config.generated.d.ts +28 -4
- package/dist/utils/composer.d.ts +3 -1
- package/dist/utils/composer.js +16 -7
- package/dist/utils/declared-type-format.d.ts +52 -0
- package/dist/utils/declared-type-format.js +233 -0
- package/dist/utils/option-format.d.ts +3 -1
- package/dist/utils/option-format.js +14 -3
- package/dist/utils/page-format.d.ts +5 -1
- package/dist/utils/page-format.js +7 -3
- package/dist/utils/plugin-sync.js +1 -1
- package/dist/utils/resource-dirs.d.ts +9 -3
- package/dist/utils/resource-dirs.js +18 -6
- package/dist/utils/template-format.d.ts +3 -1
- package/dist/utils/template-format.js +10 -3
- package/dist/utils/theme-css.d.ts +19 -0
- package/dist/utils/theme-css.js +100 -0
- package/oclif.manifest.json +3542 -1087
- package/package.json +40 -21
- package/schemas/global-config.schema.json +2 -6
- package/schemas/project-config.schema.json +37 -7
- package/dist/commands/seo/diff.js +0 -5
- package/dist/commands/seo/list.d.ts +0 -18
- package/dist/commands/seo/list.js +0 -58
- package/dist/commands/seo/pull.d.ts +0 -17
- package/dist/commands/seo/pull.js +0 -87
- package/dist/commands/seo/push.d.ts +0 -23
- package/dist/commands/seo/push.js +0 -176
- package/dist/commands/seo/rollback.js +0 -5
- package/dist/commands/template/push.js +0 -42
- package/dist/utils/seo-format.d.ts +0 -29
- package/dist/utils/seo-format.js +0 -18
- /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
- /package/dist/commands/{seo → cpt}/rollback.d.ts +0 -0
|
@@ -8,15 +8,15 @@ export declare class ProjectConfigManager {
|
|
|
8
8
|
id: string;
|
|
9
9
|
});
|
|
10
10
|
getConfigFilePath(): string;
|
|
11
|
-
getCurrentEnv(): EnvironmentConfig | null;
|
|
12
11
|
getCurrentProject(): null | (ProjectConfig & {
|
|
13
12
|
id: string;
|
|
14
13
|
});
|
|
14
|
+
getDefaultEnvironment(projectId: string): EnvironmentConfig | null;
|
|
15
15
|
getEnvironment(projectId: string, envName: string): EnvironmentConfig | null;
|
|
16
16
|
getProject(id: string): null | ProjectConfig;
|
|
17
17
|
isTelemetryDisabled(): boolean;
|
|
18
18
|
listEnvironments(projectId: string): Array<EnvironmentConfig & {
|
|
19
|
-
|
|
19
|
+
isDefault: boolean;
|
|
20
20
|
}>;
|
|
21
21
|
listProjects(): Array<ProjectConfig & {
|
|
22
22
|
id: string;
|
|
@@ -26,7 +26,7 @@ export declare class ProjectConfigManager {
|
|
|
26
26
|
removeEnvironment(projectId: string, envName: string): void;
|
|
27
27
|
removeProject(id: string): void;
|
|
28
28
|
setConfigDir(configDir: string): void;
|
|
29
|
-
setCurrent(projectId: string
|
|
29
|
+
setCurrent(projectId: string): void;
|
|
30
30
|
setEnvironment(projectId: string, envName: string, env: EnvironmentConfig): void;
|
|
31
31
|
setEnvironmentApiId(projectId: string, envName: string, apiEnvironmentId: string): void;
|
|
32
32
|
setProject(id: string, project: ProjectConfig): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { LOCAL_ENVIRONMENT_NAME } from '../lib/wp-client.js';
|
|
3
4
|
import { toSlug } from '../utils/to-slug.js';
|
|
4
5
|
import { readJsonFile, writeJsonFileAtomic } from './json-file.js';
|
|
5
6
|
export class ProjectConfigManager {
|
|
@@ -39,15 +40,6 @@ export class ProjectConfigManager {
|
|
|
39
40
|
getConfigFilePath() {
|
|
40
41
|
return join(this.requireConfigDir(), 'config.json');
|
|
41
42
|
}
|
|
42
|
-
getCurrentEnv() {
|
|
43
|
-
const config = this.readConfig();
|
|
44
|
-
if (!config.currentProject)
|
|
45
|
-
return null;
|
|
46
|
-
const project = config.projects[config.currentProject.id];
|
|
47
|
-
if (!project)
|
|
48
|
-
return null;
|
|
49
|
-
return project.environments[config.currentProject.env] ?? null;
|
|
50
|
-
}
|
|
51
43
|
getCurrentProject() {
|
|
52
44
|
const config = this.readConfig();
|
|
53
45
|
if (!config.currentProject)
|
|
@@ -57,6 +49,17 @@ export class ProjectConfigManager {
|
|
|
57
49
|
return null;
|
|
58
50
|
return { ...project, id: config.currentProject.id };
|
|
59
51
|
}
|
|
52
|
+
// The environment a command targets without --env: "local" when the project has one (where
|
|
53
|
+
// you work, so it's the safe default), otherwise its only environment. Null when the project
|
|
54
|
+
// has several remote ones and no local: the user must say which one with --env, since a
|
|
55
|
+
// remembered choice is exactly the hidden state that sends a push to the wrong site.
|
|
56
|
+
getDefaultEnvironment(projectId) {
|
|
57
|
+
const environments = this.getProject(projectId)?.environments ?? {};
|
|
58
|
+
const names = Object.keys(environments);
|
|
59
|
+
if (Object.hasOwn(environments, LOCAL_ENVIRONMENT_NAME))
|
|
60
|
+
return environments[LOCAL_ENVIRONMENT_NAME];
|
|
61
|
+
return names.length === 1 ? environments[names[0]] : null;
|
|
62
|
+
}
|
|
60
63
|
getEnvironment(projectId, envName) {
|
|
61
64
|
const project = this.getProject(projectId);
|
|
62
65
|
if (!project)
|
|
@@ -71,14 +74,11 @@ export class ProjectConfigManager {
|
|
|
71
74
|
return this.readConfig().telemetry?.disabled ?? false;
|
|
72
75
|
}
|
|
73
76
|
listEnvironments(projectId) {
|
|
74
|
-
const
|
|
75
|
-
const project = config.projects[projectId];
|
|
77
|
+
const project = this.getProject(projectId);
|
|
76
78
|
if (!project)
|
|
77
79
|
return [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
isCurrent: config.currentProject?.id === projectId && config.currentProject.env === env.name,
|
|
81
|
-
}));
|
|
80
|
+
const defaultEnv = this.getDefaultEnvironment(projectId);
|
|
81
|
+
return Object.values(project.environments).map((env) => ({ ...env, isDefault: env.name === defaultEnv?.name }));
|
|
82
82
|
}
|
|
83
83
|
listProjects() {
|
|
84
84
|
const config = this.readConfig();
|
|
@@ -101,10 +101,6 @@ export class ProjectConfigManager {
|
|
|
101
101
|
if (!project)
|
|
102
102
|
return;
|
|
103
103
|
Reflect.deleteProperty(project.environments, envName);
|
|
104
|
-
if (config.currentProject?.id === projectId && config.currentProject.env === envName) {
|
|
105
|
-
const remaining = Object.keys(project.environments);
|
|
106
|
-
config.currentProject = remaining.length > 0 ? { env: remaining[0], id: projectId } : null;
|
|
107
|
-
}
|
|
108
104
|
this.writeConfig(config);
|
|
109
105
|
}
|
|
110
106
|
removeProject(id) {
|
|
@@ -112,9 +108,7 @@ export class ProjectConfigManager {
|
|
|
112
108
|
Reflect.deleteProperty(config.projects, id);
|
|
113
109
|
if (config.currentProject?.id === id) {
|
|
114
110
|
const [nextId] = Object.keys(config.projects);
|
|
115
|
-
|
|
116
|
-
const [nextEnv] = nextProject ? Object.keys(nextProject.environments) : [];
|
|
117
|
-
config.currentProject = nextId && nextEnv ? { env: nextEnv, id: nextId } : null;
|
|
111
|
+
config.currentProject = nextId ? { id: nextId } : null;
|
|
118
112
|
}
|
|
119
113
|
this.writeConfig(config);
|
|
120
114
|
}
|
|
@@ -124,11 +118,11 @@ export class ProjectConfigManager {
|
|
|
124
118
|
setConfigDir(configDir) {
|
|
125
119
|
this.configDir = configDir;
|
|
126
120
|
}
|
|
127
|
-
setCurrent(projectId
|
|
121
|
+
setCurrent(projectId) {
|
|
128
122
|
const config = this.readConfig();
|
|
129
123
|
if (!Object.hasOwn(config.projects, projectId))
|
|
130
124
|
return;
|
|
131
|
-
config.currentProject = {
|
|
125
|
+
config.currentProject = { id: projectId };
|
|
132
126
|
this.writeConfig(config);
|
|
133
127
|
}
|
|
134
128
|
setEnvironment(projectId, envName, env) {
|
|
@@ -137,7 +131,7 @@ export class ProjectConfigManager {
|
|
|
137
131
|
if (!project)
|
|
138
132
|
return;
|
|
139
133
|
project.environments[envName] = env;
|
|
140
|
-
config.currentProject ??= {
|
|
134
|
+
config.currentProject ??= { id: projectId };
|
|
141
135
|
this.writeConfig(config);
|
|
142
136
|
}
|
|
143
137
|
setEnvironmentApiId(projectId, envName, apiEnvironmentId) {
|
|
@@ -151,11 +145,7 @@ export class ProjectConfigManager {
|
|
|
151
145
|
setProject(id, project) {
|
|
152
146
|
const config = this.readConfig();
|
|
153
147
|
config.projects[id] = project;
|
|
154
|
-
|
|
155
|
-
const [firstEnv] = Object.keys(project.environments);
|
|
156
|
-
if (firstEnv)
|
|
157
|
-
config.currentProject = { env: firstEnv, id };
|
|
158
|
-
}
|
|
148
|
+
config.currentProject ??= { id };
|
|
159
149
|
this.writeConfig(config);
|
|
160
150
|
}
|
|
161
151
|
setProjectApiId(id, apiProjectId) {
|
|
@@ -203,7 +193,8 @@ export class ProjectConfigManager {
|
|
|
203
193
|
if (value === null || typeof value !== 'object')
|
|
204
194
|
return null;
|
|
205
195
|
const pointer = value;
|
|
206
|
-
|
|
196
|
+
// A legacy `env` field, from when switch also picked the environment, is dropped here.
|
|
197
|
+
return typeof pointer.id === 'string' ? { id: pointer.id } : null;
|
|
207
198
|
}
|
|
208
199
|
sanitizeProjects(value) {
|
|
209
200
|
if (typeof value !== 'object' || value === null)
|
package/dist/hooks/finally.d.ts
CHANGED
package/dist/hooks/finally.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { settings } from '@oclif/core';
|
|
2
|
+
import { RequestError } from 'got';
|
|
1
3
|
import { isTelemetryDisabled, redactArgv, resolveEnvironment, runtimeContext, scrubEvent, SENTRY_DSN, } from '../lib/sentry.js';
|
|
2
4
|
// oclif has no `command_error` hook (checked @oclif/core@4.11.11's hooks.d.ts). `finally`
|
|
3
5
|
// is the closest equivalent: it always runs at the end of the CLI lifecycle and carries
|
|
@@ -7,8 +9,15 @@ import { isTelemetryDisabled, redactArgv, resolveEnvironment, runtimeContext, sc
|
|
|
7
9
|
// It's a heavy module (@opentelemetry deps, import-in-the-middle instrumentation), so loading
|
|
8
10
|
// it eagerly on every command would tax the common case where commands succeed.
|
|
9
11
|
const hook = async function (options) {
|
|
10
|
-
if (!options.error
|
|
12
|
+
if (!options.error)
|
|
11
13
|
return;
|
|
14
|
+
if (!isTelemetryDisabled())
|
|
15
|
+
await report(this, options);
|
|
16
|
+
// After the report, so Sentry still gets the whole chain. LPS_DEBUG=1 keeps it on screen too.
|
|
17
|
+
if (!settings.debug)
|
|
18
|
+
dropHttpCause(options.error);
|
|
19
|
+
};
|
|
20
|
+
async function report(context, options) {
|
|
12
21
|
try {
|
|
13
22
|
const Sentry = await import('@sentry/node');
|
|
14
23
|
Sentry.init({
|
|
@@ -17,11 +26,19 @@ const hook = async function (options) {
|
|
|
17
26
|
beforeSend: scrubEvent,
|
|
18
27
|
dsn: SENTRY_DSN,
|
|
19
28
|
environment: resolveEnvironment(),
|
|
20
|
-
release:
|
|
29
|
+
release: context.config.version,
|
|
30
|
+
// Sentry 11 collects all of these by default. Headers carry the app password, stack frame
|
|
31
|
+
// locals can hold it too, and this reports from users' own machines: collect none of it.
|
|
32
|
+
dataCollection: {
|
|
33
|
+
cookies: false,
|
|
34
|
+
httpBodies: [],
|
|
35
|
+
httpHeaders: false,
|
|
36
|
+
stackFrameVariables: false,
|
|
37
|
+
urlQueryParams: false,
|
|
38
|
+
userInfo: false,
|
|
39
|
+
},
|
|
21
40
|
// Node defaults `server_name` to os.hostname(), which is often the user's real name
|
|
22
|
-
// (e.g. "jane-doe-macbook-pro").
|
|
23
|
-
// default but set explicitly since this reports from users' own machines.
|
|
24
|
-
sendDefaultPii: false,
|
|
41
|
+
// (e.g. "jane-doe-macbook-pro").
|
|
25
42
|
serverName: 'loopress',
|
|
26
43
|
});
|
|
27
44
|
Sentry.captureException(options.error, {
|
|
@@ -32,7 +49,21 @@ const hook = async function (options) {
|
|
|
32
49
|
await Sentry.flush(2000);
|
|
33
50
|
}
|
|
34
51
|
catch (error) {
|
|
35
|
-
|
|
52
|
+
context.debug('Failed to report error to Sentry: %O', error);
|
|
36
53
|
}
|
|
37
|
-
}
|
|
54
|
+
}
|
|
55
|
+
// WpClient and ApiClient already fold what matters of a got error (status, the server's own
|
|
56
|
+
// reason) into their own message, and keep got's error as `cause` for the helpers that read
|
|
57
|
+
// its response. oclif prints every cause after the message, so without this a clear "Request
|
|
58
|
+
// failed (409) on ...: The active theme is not a block theme" ends with "Caused by: HTTPError:
|
|
59
|
+
// Request failed with status code 409 ... Code: ERR_NON_2XX_3XX_RESPONSE", which is what the
|
|
60
|
+
// user remembers. Other causes (a failed plugin install under its fallback advice) stay.
|
|
61
|
+
export function dropHttpCause(error) {
|
|
62
|
+
for (let current = error; current instanceof Error; current = current.cause) {
|
|
63
|
+
if (current.cause instanceof RequestError) {
|
|
64
|
+
delete current.cause;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
38
69
|
export default hook;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import got from 'got';
|
|
2
|
+
import { withSpinner } from './spinner.js';
|
|
2
3
|
import { REQUEST_TIMEOUT_MS } from './wp-client.js';
|
|
3
4
|
export const API_URL = process.env.LPS_API_URL ?? 'https://api.loopress.dev';
|
|
4
5
|
/**
|
|
@@ -29,7 +30,7 @@ export class ApiClient {
|
|
|
29
30
|
}
|
|
30
31
|
async request(method, path, json) {
|
|
31
32
|
try {
|
|
32
|
-
const response = await this.client(path, { json, method });
|
|
33
|
+
const response = await withSpinner('Waiting for the Loopress API', async () => this.client(path, { json, method }));
|
|
33
34
|
return (response.body ? JSON.parse(response.body) : undefined);
|
|
34
35
|
}
|
|
35
36
|
catch (error) {
|
package/dist/lib/app-build.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
|
+
import { withSpinner } from './spinner.js';
|
|
5
6
|
// The package managers `lps app push` may spawn. An allowlist, not the raw `packageManager`
|
|
6
7
|
// field: the name ends up as a process to run.
|
|
7
8
|
const PACKAGE_MANAGERS = ['bun', 'npm', 'pnpm', 'yarn'];
|
|
@@ -43,7 +44,7 @@ export async function buildApp(appDir, onStart) {
|
|
|
43
44
|
const pm = detectPackageManager(appDir, pkg.packageManager);
|
|
44
45
|
const command = `${pm} run build`;
|
|
45
46
|
onStart?.(command);
|
|
46
|
-
const
|
|
47
|
+
const build = new Promise((resolve, reject) => {
|
|
47
48
|
// Windows resolves `npm` to `npm.cmd`, which only a shell finds. `pm` comes from the
|
|
48
49
|
// allowlist above, so the shell never sees user input.
|
|
49
50
|
const child = spawn(pm, ['run', 'build'], { cwd: appDir, shell: process.platform === 'win32' });
|
|
@@ -58,6 +59,7 @@ export async function buildApp(appDir, onStart) {
|
|
|
58
59
|
resolve({ code: exitCode, output: out });
|
|
59
60
|
});
|
|
60
61
|
});
|
|
62
|
+
const { code, output } = await withSpinner(`Running ${command}`, async () => build);
|
|
61
63
|
if (code !== 0) {
|
|
62
64
|
const tail = output.trimEnd().split('\n').slice(-OUTPUT_TAIL_LINES).join('\n');
|
|
63
65
|
const hint = existsSync(join(appDir, 'node_modules'))
|
package/dist/lib/app-manifest.js
CHANGED
|
@@ -22,7 +22,7 @@ export function isAllowedAsset(relPath) {
|
|
|
22
22
|
// server would reject, so the push fails locally with a clear message instead of a 400
|
|
23
23
|
// halfway through the upload.
|
|
24
24
|
export async function buildFileList(distDir) {
|
|
25
|
-
const relPaths = (await walkFiles(distDir)).
|
|
25
|
+
const relPaths = (await walkFiles(distDir)).toSorted((a, b) => a.localeCompare(b));
|
|
26
26
|
const files = [];
|
|
27
27
|
for (const path of relPaths) {
|
|
28
28
|
if (!isAllowedAsset(path)) {
|
|
@@ -37,7 +37,7 @@ export async function buildFileList(distDir) {
|
|
|
37
37
|
// Used as the enqueue `?ver` (cache busting) and as the build identity in `app list`.
|
|
38
38
|
export function computeBuildId(files) {
|
|
39
39
|
const digest = createHash('sha256');
|
|
40
|
-
for (const file of
|
|
40
|
+
for (const file of files.toSorted((a, b) => a.path.localeCompare(b.path))) {
|
|
41
41
|
digest.update(`${file.path}:${file.sha256}\n`);
|
|
42
42
|
}
|
|
43
43
|
return digest.digest('hex').slice(0, 12);
|
package/dist/lib/base.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare abstract class LoopressCommand extends Command {
|
|
|
14
14
|
};
|
|
15
15
|
protected dryRun: boolean;
|
|
16
16
|
protected localConfig: LoopressLocalConfig;
|
|
17
|
+
protected productionConfirmed: boolean;
|
|
17
18
|
protected projectId: string;
|
|
18
19
|
protected siteConfig: EnvironmentConfig;
|
|
19
20
|
protected yes: boolean;
|
|
@@ -38,10 +39,9 @@ export declare abstract class LoopressCommand extends Command {
|
|
|
38
39
|
protected resolveHooksPath(override?: string): string;
|
|
39
40
|
protected resolveMenuPath(override?: string): string;
|
|
40
41
|
protected resolveOptionsPath(override?: string): string;
|
|
41
|
-
protected resolveSeoPath(override?: string): string;
|
|
42
42
|
protected resolveSnippetsPath(override?: string): string;
|
|
43
43
|
protected resolveThemeStylesPath(override?: string): string;
|
|
44
|
+
protected warnRemovedSeoModule(): void;
|
|
44
45
|
private pickEnvironment;
|
|
45
46
|
private resolveEnvironment;
|
|
46
|
-
private resolveEnvironmentForConfiguredProject;
|
|
47
47
|
}
|
package/dist/lib/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { confirm } from '@inquirer/prompts';
|
|
2
2
|
import { Command, Flags } from '@oclif/core';
|
|
3
3
|
import { Listr } from 'listr2';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
4
5
|
import { mkdir, rm } from 'node:fs/promises';
|
|
5
6
|
import { join } from 'node:path';
|
|
6
7
|
import { configManager } from '../config/project-config.manager.js';
|
|
@@ -9,13 +10,14 @@ import { pluralize } from '../utils/pluralize.js';
|
|
|
9
10
|
import { resolveResourceDir } from '../utils/resource-dirs.js';
|
|
10
11
|
import { isInteractive } from './interactive.js';
|
|
11
12
|
import { isAppPasswordStale, rotateAppPassword } from './rotate-app-password.js';
|
|
13
|
+
import { withoutSpinner } from './spinner.js';
|
|
12
14
|
import { WpClient } from './wp-client.js';
|
|
13
15
|
export class LoopressCommand extends Command {
|
|
14
|
-
// On every subclass without opt-in
|
|
15
|
-
//
|
|
16
|
+
// On every subclass without opt-in. Without it, commands target the "local" environment, so
|
|
17
|
+
// reaching a remote site is always spelled out in the command itself.
|
|
16
18
|
static baseFlags = {
|
|
17
19
|
env: Flags.string({
|
|
18
|
-
description: 'Target environment by name,
|
|
20
|
+
description: 'Target environment by name (default: "local", or the project\'s only environment)',
|
|
19
21
|
}),
|
|
20
22
|
};
|
|
21
23
|
static dryRunFlag = {
|
|
@@ -26,6 +28,8 @@ export class LoopressCommand extends Command {
|
|
|
26
28
|
};
|
|
27
29
|
dryRun = false;
|
|
28
30
|
localConfig = {};
|
|
31
|
+
// Set by PushCommand's hidden --production-confirmed, see there.
|
|
32
|
+
productionConfirmed = false;
|
|
29
33
|
projectId;
|
|
30
34
|
siteConfig;
|
|
31
35
|
yes = false;
|
|
@@ -54,6 +58,7 @@ export class LoopressCommand extends Command {
|
|
|
54
58
|
}));
|
|
55
59
|
this.dryRun = Boolean(flags['dry-run']);
|
|
56
60
|
this.yes = Boolean(flags.yes);
|
|
61
|
+
this.productionConfirmed = Boolean(flags['production-confirmed']);
|
|
57
62
|
this.localConfig = await readLocalConfig();
|
|
58
63
|
const resolved = this.resolveEnvironment(flags.env);
|
|
59
64
|
this.siteConfig = resolved.env;
|
|
@@ -104,13 +109,15 @@ export class LoopressCommand extends Command {
|
|
|
104
109
|
}
|
|
105
110
|
if (options.alwaysCreateDir === true || items.length > 0)
|
|
106
111
|
await mkdir(dir, { recursive: true });
|
|
107
|
-
await
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
await withoutSpinner(async () => {
|
|
113
|
+
await new Listr(items.map((item) => ({
|
|
114
|
+
async task(_ctx, task) {
|
|
115
|
+
await options.write(item, dir);
|
|
116
|
+
task.output = `Pulled: ${options.title(item)}`;
|
|
117
|
+
},
|
|
118
|
+
title: `Pull ${options.title(item)}`,
|
|
119
|
+
})), { renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
120
|
+
});
|
|
114
121
|
await this.removeOrphanedFiles(dir, orphans, options.orphanReason);
|
|
115
122
|
this.log(options.pulledMessage);
|
|
116
123
|
}
|
|
@@ -162,15 +169,19 @@ export class LoopressCommand extends Command {
|
|
|
162
169
|
resolveOptionsPath(override) {
|
|
163
170
|
return resolveResourceDir('options', this.localConfig, override);
|
|
164
171
|
}
|
|
165
|
-
resolveSeoPath(override) {
|
|
166
|
-
return resolveResourceDir('seo', this.localConfig, override);
|
|
167
|
-
}
|
|
168
172
|
resolveSnippetsPath(override) {
|
|
169
173
|
return resolveResourceDir('snippets', this.localConfig, override);
|
|
170
174
|
}
|
|
171
175
|
resolveThemeStylesPath(override) {
|
|
172
176
|
return resolveResourceDir('themeStyles', this.localConfig, override);
|
|
173
177
|
}
|
|
178
|
+
// `lps seo` was removed: a leftover seo/ folder or seoDir key would otherwise be silently
|
|
179
|
+
// ignored by `lps pull` / `lps push`. Remove once projects have had a release to migrate.
|
|
180
|
+
warnRemovedSeoModule() {
|
|
181
|
+
if (!('seoDir' in this.localConfig) && !existsSync(join(this.rootDir, 'seo')))
|
|
182
|
+
return;
|
|
183
|
+
this.warn('The SEO module was removed: seo/ is no longer pulled or pushed. Track SEO plugin settings with `lps option add rank-math-options-titles` (or `wpseo_titles`), then delete seo/ and any seoDir key from loopress.json.');
|
|
184
|
+
}
|
|
174
185
|
pickEnvironment(project, envName) {
|
|
175
186
|
const env = project.environments[envName];
|
|
176
187
|
if (!env) {
|
|
@@ -178,24 +189,12 @@ export class LoopressCommand extends Command {
|
|
|
178
189
|
}
|
|
179
190
|
return env;
|
|
180
191
|
}
|
|
192
|
+
// Order: --env, then the project's default (see getDefaultEnvironment). The project comes from
|
|
193
|
+
// loopress.json when pinned, from `lps project switch` otherwise.
|
|
181
194
|
resolveEnvironment(envName) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (envName) {
|
|
186
|
-
const current = configManager.getCurrentProject();
|
|
187
|
-
if (!current) {
|
|
188
|
-
this.error('No project configured. Run `lps project config` first.');
|
|
189
|
-
}
|
|
190
|
-
return { env: this.pickEnvironment(current, envName), projectId: current.id };
|
|
191
|
-
}
|
|
192
|
-
const env = configManager.getCurrentEnv();
|
|
193
|
-
const current = configManager.getCurrentProject();
|
|
194
|
-
if (env && current)
|
|
195
|
-
return { env, projectId: current.id };
|
|
196
|
-
this.error('No environment configured. Run `lps project config` first.');
|
|
197
|
-
}
|
|
198
|
-
resolveEnvironmentForConfiguredProject(projectId, envName) {
|
|
195
|
+
const projectId = this.localConfig.projectId ?? configManager.getCurrentProject()?.id;
|
|
196
|
+
if (!projectId)
|
|
197
|
+
this.error('No project configured. Run `lps project config` first.');
|
|
199
198
|
const project = configManager.getProject(projectId);
|
|
200
199
|
if (!project) {
|
|
201
200
|
this.error(`Project "${projectId}" (from loopress.json) not found. Run \`lps project config\` to configure it.`);
|
|
@@ -206,14 +205,10 @@ export class LoopressCommand extends Command {
|
|
|
206
205
|
}
|
|
207
206
|
if (envName)
|
|
208
207
|
return { env: this.pickEnvironment(project, envName), projectId };
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const current = configManager.getCurrentProject();
|
|
213
|
-
const currentEnv = current?.id === projectId ? configManager.getCurrentEnv() : null;
|
|
214
|
-
if (!currentEnv) {
|
|
215
|
-
this.error(`Project "${project.name}" has multiple environments. Run \`lps project switch\` to pick one.`);
|
|
208
|
+
const env = configManager.getDefaultEnvironment(projectId);
|
|
209
|
+
if (!env) {
|
|
210
|
+
this.error(`Project "${project.name}" has no "local" environment, pass --env to pick one. Available: ${envNames.join(', ')}`);
|
|
216
211
|
}
|
|
217
|
-
return { env
|
|
212
|
+
return { env, projectId };
|
|
218
213
|
}
|
|
219
214
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type DeclaredTypeSpec, type RegisteredItem } from '../utils/declared-type-format.js';
|
|
2
|
+
import { LoopressCommand } from './base.js';
|
|
3
|
+
import { type CommandClass } from './php-files-command.js';
|
|
4
|
+
import { PushCommand } from './push-command.js';
|
|
5
|
+
type PushResult = {
|
|
6
|
+
path: string;
|
|
7
|
+
pushed: string[];
|
|
8
|
+
status: 'dry-run' | 'success';
|
|
9
|
+
};
|
|
10
|
+
type PullResult = {
|
|
11
|
+
path: string;
|
|
12
|
+
status: 'dry-run' | 'success';
|
|
13
|
+
};
|
|
14
|
+
type RmResult = {
|
|
15
|
+
removed: boolean;
|
|
16
|
+
slug: string;
|
|
17
|
+
status: 'aborted' | 'dry-run' | 'success';
|
|
18
|
+
};
|
|
19
|
+
export type DeclaredPushCommand = Omit<PushCommand, 'run'> & {
|
|
20
|
+
run(): Promise<PushResult>;
|
|
21
|
+
};
|
|
22
|
+
export declare function declaredPushCommand(spec: DeclaredTypeSpec, description: string): CommandClass<DeclaredPushCommand>;
|
|
23
|
+
export type DeclaredPullCommand = Omit<LoopressCommand, 'run'> & {
|
|
24
|
+
run(): Promise<PullResult>;
|
|
25
|
+
};
|
|
26
|
+
export declare function declaredPullCommand(spec: DeclaredTypeSpec, description: string): CommandClass<DeclaredPullCommand>;
|
|
27
|
+
export type DeclaredListCommand = Omit<LoopressCommand, 'run'> & {
|
|
28
|
+
run(): Promise<RegisteredItem[]>;
|
|
29
|
+
};
|
|
30
|
+
export declare function declaredListCommand(spec: DeclaredTypeSpec, description: string, countLabel: (count: number) => string): CommandClass<DeclaredListCommand>;
|
|
31
|
+
export type DeclaredRmCommand = Omit<LoopressCommand, 'run'> & {
|
|
32
|
+
run(): Promise<RmResult>;
|
|
33
|
+
};
|
|
34
|
+
export declare function declaredRmCommand(spec: DeclaredTypeSpec, description: string, keptNote: string): CommandClass<DeclaredRmCommand>;
|
|
35
|
+
export {};
|