@loopress/cli 0.6.0 → 0.7.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.
Files changed (35) hide show
  1. package/README.md +71 -90
  2. package/dist/commands/composer/pull.js +1 -1
  3. package/dist/commands/composer/push.js +1 -1
  4. package/dist/commands/init.js +15 -6
  5. package/dist/commands/login.js +1 -1
  6. package/dist/commands/logout.js +1 -1
  7. package/dist/commands/plugin/add.d.ts +0 -2
  8. package/dist/commands/plugin/add.js +2 -23
  9. package/dist/commands/plugin/pull.js +1 -1
  10. package/dist/commands/plugin/push.js +3 -3
  11. package/dist/commands/project/config.d.ts +1 -0
  12. package/dist/commands/project/config.js +36 -17
  13. package/dist/commands/project/list.js +4 -5
  14. package/dist/commands/project/remove.js +33 -14
  15. package/dist/commands/project/switch.d.ts +2 -0
  16. package/dist/commands/project/switch.js +28 -9
  17. package/dist/commands/snippet/list.js +3 -3
  18. package/dist/commands/snippet/pull.d.ts +1 -1
  19. package/dist/commands/snippet/pull.js +7 -6
  20. package/dist/commands/snippet/push.d.ts +2 -2
  21. package/dist/commands/snippet/push.js +47 -15
  22. package/dist/commands/{project/remove-env.d.ts → status.d.ts} +3 -1
  23. package/dist/commands/status.js +66 -0
  24. package/dist/config/project-config.manager.d.ts +17 -10
  25. package/dist/config/project-config.manager.js +91 -44
  26. package/dist/config/types.d.ts +5 -2
  27. package/dist/lib/base.js +13 -3
  28. package/dist/types/snippet.d.ts +2 -0
  29. package/dist/utils/snippet-plugin.d.ts +2 -1
  30. package/dist/utils/snippet-plugin.js +5 -4
  31. package/oclif.manifest.json +149 -175
  32. package/package.json +17 -2
  33. package/dist/commands/project/remove-env.js +0 -33
  34. package/dist/commands/project/switch-env.d.ts +0 -6
  35. package/dist/commands/project/switch-env.js +0 -33
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
3
  import { homedir } from 'node:os';
3
4
  import { join } from 'node:path';
@@ -13,6 +14,9 @@ export class ProjectConfigManager {
13
14
  }
14
15
  return ProjectConfigManager.instance;
15
16
  }
17
+ createProjectId() {
18
+ return randomUUID();
19
+ }
16
20
  ensureConfigDir() {
17
21
  const dir = join(this.homeDir, '.loopress');
18
22
  if (!existsSync(dir)) {
@@ -23,41 +27,49 @@ export class ProjectConfigManager {
23
27
  return join(this.homeDir, '.loopress', 'config.json');
24
28
  }
25
29
  getCurrentEnv() {
26
- const project = this.getCurrentProject();
27
- if (!project || !project.currentEnv)
30
+ const config = this.readConfig();
31
+ if (!config.currentProject)
32
+ return null;
33
+ const project = config.projects[config.currentProject.id];
34
+ if (!project)
28
35
  return null;
29
- return project.environments[project.currentEnv] ?? null;
36
+ return project.environments[config.currentProject.env] ?? null;
30
37
  }
31
38
  getCurrentProject() {
32
39
  const config = this.readConfig();
33
- if (!config.currentProject || !config.projects[config.currentProject])
40
+ if (!config.currentProject)
41
+ return null;
42
+ const project = config.projects[config.currentProject.id];
43
+ if (!project)
34
44
  return null;
35
- return config.projects[config.currentProject];
45
+ return { ...project, id: config.currentProject.id };
36
46
  }
37
- getEnvironment(projectName, envName) {
38
- const project = this.getProject(projectName);
47
+ getEnvironment(projectId, envName) {
48
+ const project = this.getProject(projectId);
39
49
  if (!project)
40
50
  return null;
41
51
  return project.environments[envName] ?? null;
42
52
  }
43
- getProject(name) {
53
+ getProject(id) {
44
54
  const config = this.readConfig();
45
- return config.projects[name] ?? null;
55
+ return config.projects[id] ?? null;
46
56
  }
47
- listEnvironments(projectName) {
48
- const project = this.getProject(projectName);
57
+ listEnvironments(projectId) {
58
+ const config = this.readConfig();
59
+ const project = config.projects[projectId];
49
60
  if (!project)
50
61
  return [];
51
62
  return Object.values(project.environments).map((env) => ({
52
63
  ...env,
53
- isCurrent: env.name === project.currentEnv,
64
+ isCurrent: config.currentProject?.id === projectId && config.currentProject.env === env.name,
54
65
  }));
55
66
  }
56
67
  listProjects() {
57
68
  const config = this.readConfig();
58
- return Object.values(config.projects).map((project) => ({
69
+ return Object.entries(config.projects).map(([id, project]) => ({
59
70
  ...project,
60
- isCurrent: project.name === config.currentProject,
71
+ id,
72
+ isCurrent: config.currentProject?.id === id,
61
73
  }));
62
74
  }
63
75
  readConfig() {
@@ -65,58 +77,62 @@ export class ProjectConfigManager {
65
77
  if (!existsSync(filePath)) {
66
78
  return { currentProject: null, projects: {} };
67
79
  }
68
- return JSON.parse(readFileSync(filePath, 'utf8'));
80
+ try {
81
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
82
+ return this.sanitizeConfig(parsed);
83
+ }
84
+ catch {
85
+ return { currentProject: null, projects: {} };
86
+ }
69
87
  }
70
- removeEnvironment(projectName, envName) {
88
+ removeEnvironment(projectId, envName) {
71
89
  const config = this.readConfig();
72
- if (!config.projects[projectName])
90
+ const project = config.projects[projectId];
91
+ if (!project)
73
92
  return;
74
- const project = config.projects[projectName];
75
93
  delete project.environments[envName];
76
- if (project.currentEnv === envName) {
94
+ if (config.currentProject?.id === projectId && config.currentProject.env === envName) {
77
95
  const remaining = Object.keys(project.environments);
78
- project.currentEnv = remaining.length > 0 ? remaining[0] : null;
96
+ config.currentProject = remaining.length > 0 ? { env: remaining[0], id: projectId } : null;
79
97
  }
80
98
  this.writeConfig(config);
81
99
  }
82
- removeProject(name) {
100
+ removeProject(id) {
83
101
  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;
102
+ delete config.projects[id];
103
+ if (config.currentProject?.id === id) {
104
+ const [nextId] = Object.keys(config.projects);
105
+ const nextProject = nextId ? config.projects[nextId] : undefined;
106
+ const [nextEnv] = nextProject ? Object.keys(nextProject.environments) : [];
107
+ config.currentProject = nextId && nextEnv ? { env: nextEnv, id: nextId } : null;
88
108
  }
89
109
  this.writeConfig(config);
90
110
  }
91
- setCurrentEnv(projectName, envName) {
111
+ setCurrent(projectId, envName) {
92
112
  const config = this.readConfig();
93
- if (!config.projects[projectName])
113
+ if (!config.projects[projectId])
94
114
  return;
95
- config.projects[projectName].currentEnv = envName;
115
+ config.currentProject = { env: envName, id: projectId };
96
116
  this.writeConfig(config);
97
117
  }
98
- setCurrentProject(name) {
118
+ setEnvironment(projectId, envName, env) {
99
119
  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])
120
+ const project = config.projects[projectId];
121
+ if (!project)
106
122
  return;
107
- const project = config.projects[projectName];
108
- const isFirst = Object.keys(project.environments).length === 0;
109
123
  project.environments[envName] = env;
110
- if (isFirst)
111
- project.currentEnv = envName;
124
+ if (!config.currentProject)
125
+ config.currentProject = { env: envName, id: projectId };
112
126
  this.writeConfig(config);
113
127
  }
114
- setProject(name, project) {
128
+ setProject(id, project) {
115
129
  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;
130
+ config.projects[id] = project;
131
+ if (!config.currentProject) {
132
+ const [firstEnv] = Object.keys(project.environments);
133
+ if (firstEnv)
134
+ config.currentProject = { env: firstEnv, id };
135
+ }
120
136
  this.writeConfig(config);
121
137
  }
122
138
  writeConfig(config) {
@@ -126,5 +142,36 @@ export class ProjectConfigManager {
126
142
  writeFileSync(tmpPath, JSON.stringify(config, null, 2));
127
143
  renameSync(tmpPath, filePath);
128
144
  }
145
+ isProjectConfig(value) {
146
+ if (typeof value !== 'object' || value === null)
147
+ return false;
148
+ const candidate = value;
149
+ return typeof candidate.name === 'string' && typeof candidate.environments === 'object' && candidate.environments !== null;
150
+ }
151
+ sanitizeConfig(value) {
152
+ if (typeof value !== 'object' || value === null)
153
+ return { currentProject: null, projects: {} };
154
+ const candidate = value;
155
+ return {
156
+ currentProject: this.sanitizeCurrentProject(candidate.currentProject),
157
+ projects: this.sanitizeProjects(candidate.projects),
158
+ };
159
+ }
160
+ sanitizeCurrentProject(value) {
161
+ if (value === null || typeof value !== 'object')
162
+ return null;
163
+ const pointer = value;
164
+ return typeof pointer.id === 'string' && typeof pointer.env === 'string' ? { env: pointer.env, id: pointer.id } : null;
165
+ }
166
+ sanitizeProjects(value) {
167
+ if (typeof value !== 'object' || value === null)
168
+ return {};
169
+ const projects = {};
170
+ for (const [id, project] of Object.entries(value)) {
171
+ if (this.isProjectConfig(project))
172
+ projects[id] = project;
173
+ }
174
+ return projects;
175
+ }
129
176
  }
130
177
  export const configManager = ProjectConfigManager.getInstance();
@@ -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: null | string;
17
+ currentProject: CurrentProjectPointer | null;
15
18
  projects: Record<string, ProjectConfig>;
16
19
  }
package/dist/lib/base.js CHANGED
@@ -33,10 +33,20 @@ export class LoopressCommand extends Command {
33
33
  if (!project) {
34
34
  this.error(`Project "${localConfig.projectId}" (from loopress.json) not found. Run \`lps project config\` to configure it.`);
35
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.`);
36
+ const envNames = Object.keys(project.environments);
37
+ if (envNames.length === 0) {
38
+ this.error(`Project "${project.name}" has no environments configured. Run \`lps project config\` to add one.`);
38
39
  }
39
- this.siteConfig = project.environments[project.currentEnv];
40
+ if (envNames.length === 1) {
41
+ this.siteConfig = project.environments[envNames[0]];
42
+ return;
43
+ }
44
+ const current = configManager.getCurrentProject();
45
+ const currentEnv = current?.id === localConfig.projectId ? configManager.getCurrentEnv() : null;
46
+ if (!currentEnv) {
47
+ this.error(`Project "${project.name}" has multiple environments. Run \`lps project switch\` to pick one.`);
48
+ }
49
+ this.siteConfig = currentEnv;
40
50
  return;
41
51
  }
42
52
  const env = configManager.getCurrentEnv();
@@ -1,6 +1,8 @@
1
+ import { SnippetType } from '../utils/snippet-plugin.js';
1
2
  export interface Snippet {
2
3
  code: string;
3
4
  id?: number;
4
5
  name: string;
5
6
  path: string;
7
+ type: SnippetType;
6
8
  }
@@ -9,9 +9,10 @@ export interface NormalizedSnippet {
9
9
  tags: string[];
10
10
  type: SnippetType;
11
11
  }
12
+ export declare function parseType(raw: unknown): null | SnippetType;
12
13
  export interface SnippetPlugin {
13
14
  endpoint(siteUrl: string): string;
14
15
  fromRemote(data: Record<string, unknown>): NormalizedSnippet;
15
- toPayload(name: string, code: string, path: string): Record<string, unknown>;
16
+ toPayload(name: string, code: string, path: string, type: SnippetType): Record<string, unknown>;
16
17
  }
17
18
  export declare function getSnippetPlugin(name: PluginName): SnippetPlugin;
@@ -1,4 +1,4 @@
1
- function parseType(raw) {
1
+ export function parseType(raw) {
2
2
  const valid = ['css', 'html', 'js', 'php', 'text'];
3
3
  const value = String(raw ?? '').toLowerCase();
4
4
  return valid.includes(value) ? value : null;
@@ -29,12 +29,13 @@ class CodeSnippetsPlugin {
29
29
  type: resolveType(data.type, String(data.code ?? '')),
30
30
  };
31
31
  }
32
- toPayload(name, code, path) {
32
+ toPayload(name, code, path, type) {
33
33
  return {
34
34
  code: code.replace(/^<\?php\s*/i, ''),
35
35
  desc: `Imported from ${path}`,
36
36
  name,
37
37
  tags: ['cli-import'],
38
+ type,
38
39
  };
39
40
  }
40
41
  }
@@ -53,13 +54,13 @@ class WPCodePlugin {
53
54
  type: resolveType(data.type, String(data.code ?? '')),
54
55
  };
55
56
  }
56
- toPayload(name, code, path) {
57
+ toPayload(name, code, path, type) {
57
58
  return {
58
59
  code,
59
60
  note: `Imported from ${path}`,
60
61
  tags: ['cli-import'],
61
62
  title: name,
62
- type: 'php',
63
+ type,
63
64
  };
64
65
  }
65
66
  }