@catladder/cli 0.0.0-refactor-cli-add-improved-git-utilities-224e6ee7 → 0.0.0-refactor-cli-add-improved-git-utilities-916545cc
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/dist/bundles/catenv/index.js +43680 -26855
- package/dist/bundles/cli/index.js +95077 -78240
- package/dist/cli/src/apps/cli/commands/project/setup/setupAccessTokens.d.ts +2 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js +3 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js.map +1 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupCloudRun.js +4 -2
- package/dist/cli/src/apps/cli/commands/project/setup/setupCloudRun.js.map +1 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupKubernetes.d.ts +2 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupKubernetes.js +23 -9
- package/dist/cli/src/apps/cli/commands/project/setup/setupKubernetes.js.map +1 -1
- package/dist/cli/src/apps/cli/commands/project/setup/setupTopic.js +6 -4
- package/dist/cli/src/apps/cli/commands/project/setup/setupTopic.js.map +1 -1
- package/dist/cli/src/gitlab/apiClient.d.ts +3 -1
- package/dist/cli/src/gitlab/apiClient.js +2 -1
- package/dist/cli/src/gitlab/apiClient.js.map +1 -1
- package/dist/cli/src/gitlab/getGitlabProjectId.d.ts +4 -0
- package/dist/cli/src/gitlab/getGitlabProjectId.js +29 -0
- package/dist/cli/src/gitlab/getGitlabProjectId.js.map +1 -0
- package/dist/cli/src/gitlab/index.d.ts +3 -0
- package/dist/cli/src/gitlab/index.js +20 -0
- package/dist/cli/src/gitlab/index.js.map +1 -0
- package/dist/cli/src/utils/gitlab.d.ts +9 -6
- package/dist/cli/src/utils/gitlab.js +59 -42
- package/dist/cli/src/utils/gitlab.js.map +1 -1
- package/dist/cli/src/utils/preferences.js +23 -13
- package/dist/cli/src/utils/preferences.js.map +1 -1
- package/dist/cli/src/utils/projectPreferences.d.ts +5 -0
- package/dist/cli/src/utils/projectPreferences.js +54 -0
- package/dist/cli/src/utils/projectPreferences.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/apps/cli/commands/project/setup/setupAccessTokens.ts +11 -1
- package/src/apps/cli/commands/project/setup/setupCloudRun.ts +4 -2
- package/src/apps/cli/commands/project/setup/setupKubernetes.ts +30 -27
- package/src/apps/cli/commands/project/setup/setupTopic.ts +6 -11
- package/src/gitlab/apiClient.ts +5 -2
- package/src/gitlab/getGitlabProjectId.ts +32 -0
- package/src/gitlab/index.ts +3 -0
- package/src/utils/gitlab.ts +88 -96
- package/src/utils/preferences.ts +40 -20
- package/src/utils/projectPreferences.ts +78 -0
package/package.json
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
}
|
|
54
54
|
],
|
|
55
55
|
"license": "MIT",
|
|
56
|
-
"version": "0.0.0-refactor-cli-add-improved-git-utilities-
|
|
56
|
+
"version": "0.0.0-refactor-cli-add-improved-git-utilities-916545cc",
|
|
57
57
|
"scripts": {
|
|
58
58
|
"lint": "eslint \"src/**/*.ts\"",
|
|
59
59
|
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { CommandInstance } from "vorpal";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
3
|
import { doGitlabRequest, getProjectInfo } from "../../../../../utils/gitlab";
|
|
4
|
+
import {
|
|
5
|
+
getGitlabProjectId,
|
|
6
|
+
gitlabApiClient,
|
|
7
|
+
type GitlabRestClient,
|
|
8
|
+
} from "../../../../../gitlab";
|
|
4
9
|
|
|
5
10
|
const TOKEN_NAME = "semantic-release";
|
|
6
11
|
|
|
7
|
-
export const setupAccessTokens = async (
|
|
12
|
+
export const setupAccessTokens = async (
|
|
13
|
+
instance: CommandInstance,
|
|
14
|
+
preinit?: [number, GitlabRestClient],
|
|
15
|
+
) => {
|
|
16
|
+
const [id, gitlabApi] =
|
|
17
|
+
preinit ?? (await Promise.all([getGitlabProjectId(), gitlabApiClient()]));
|
|
8
18
|
const { id: projectId } = await getProjectInfo(instance);
|
|
9
19
|
|
|
10
20
|
const projectTokens = await doGitlabRequest(
|
|
@@ -6,27 +6,30 @@ import {
|
|
|
6
6
|
import type { CommandInstance } from "vorpal";
|
|
7
7
|
import { exec } from "child-process-promise";
|
|
8
8
|
import { connectToCluster } from "../../../../../utils/cluster";
|
|
9
|
-
import {
|
|
10
|
-
doGitlabRequest,
|
|
11
|
-
getProjectInfo,
|
|
12
|
-
upsertAllVariables,
|
|
13
|
-
} from "../../../../../utils/gitlab";
|
|
9
|
+
import { upsertAllVariables } from "../../../../../utils/gitlab";
|
|
14
10
|
import ensureNamespace from "../utils/ensureNamespace";
|
|
11
|
+
import type { GitlabRestClient } from "../../../../../gitlab";
|
|
12
|
+
import { getGitlabProjectId, gitlabApiClient } from "../../../../../gitlab";
|
|
15
13
|
|
|
16
14
|
export const setupKubernetes = async (
|
|
17
15
|
instance: CommandInstance,
|
|
18
16
|
context: ComponentContext,
|
|
17
|
+
preinit?: [number, GitlabRestClient],
|
|
19
18
|
) => {
|
|
20
19
|
const deployConfig = context.deploy?.config;
|
|
21
20
|
if (!isOfDeployType(deployConfig, "kubernetes")) {
|
|
22
21
|
throw new Error("cannot run setupKubernetes on non-kubernetes deployments");
|
|
23
22
|
}
|
|
23
|
+
const [id, gitlabApi] =
|
|
24
|
+
preinit ?? (await Promise.all([getGitlabProjectId(), gitlabApiClient()]));
|
|
24
25
|
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const deploy_tokens = await gitlabApi.DeployTokens.all({ projectId: id });
|
|
27
|
+
|
|
28
|
+
// const { id: projectId } = await getProjectInfo(instance);
|
|
29
|
+
// const deploy_tokens = await doGitlabRequest(
|
|
30
|
+
// instance,
|
|
31
|
+
// `projects/${projectId}/deploy_tokens`,
|
|
32
|
+
// );
|
|
30
33
|
|
|
31
34
|
if (
|
|
32
35
|
!deploy_tokens.find(
|
|
@@ -36,17 +39,21 @@ export const setupKubernetes = async (
|
|
|
36
39
|
instance.log(
|
|
37
40
|
"I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project.",
|
|
38
41
|
);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
id: projectId,
|
|
45
|
-
name: "gitlab-deploy-token",
|
|
46
|
-
scopes: ["read_registry"],
|
|
47
|
-
},
|
|
48
|
-
"POST",
|
|
42
|
+
await gitlabApi.DeployTokens.create(
|
|
43
|
+
"gitlab-deploy-token",
|
|
44
|
+
["read_registry"],
|
|
45
|
+
{ projectId: id },
|
|
49
46
|
);
|
|
47
|
+
// await doGitlabRequest(
|
|
48
|
+
// instance,
|
|
49
|
+
// `projects/${projectId}/deploy_tokens`,
|
|
50
|
+
// {
|
|
51
|
+
// id: projectId,
|
|
52
|
+
// name: "gitlab-deploy-token",
|
|
53
|
+
// scopes: ["read_registry"],
|
|
54
|
+
// },
|
|
55
|
+
// "POST",
|
|
56
|
+
// );
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
const fullName = getFullKubernetesClusterName(deployConfig.cluster);
|
|
@@ -160,12 +167,8 @@ EOF
|
|
|
160
167
|
instance.log("");
|
|
161
168
|
instance.log("pusing secrets to gitlab...");
|
|
162
169
|
|
|
163
|
-
await upsertAllVariables(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
context.env,
|
|
167
|
-
context.name,
|
|
168
|
-
false, // no backup
|
|
169
|
-
);
|
|
170
|
+
await upsertAllVariables(instance, vars, context.env, context.name, {
|
|
171
|
+
backup: false,
|
|
172
|
+
});
|
|
170
173
|
instance.log("done!");
|
|
171
174
|
};
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import type { CommandInstance } from "vorpal";
|
|
2
|
-
import {
|
|
2
|
+
import { getGitlabProjectInfo, gitlabApiClient } from "../../../../../gitlab";
|
|
3
3
|
|
|
4
4
|
const catladderTopic = "catladder";
|
|
5
5
|
|
|
6
6
|
export const setupTopic = async (instance: CommandInstance) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
instance,
|
|
13
|
-
`projects/${projectId}`,
|
|
14
|
-
{ topics: [catladderTopic, ...topics] },
|
|
15
|
-
"PUT",
|
|
16
|
-
);
|
|
7
|
+
const api = await gitlabApiClient();
|
|
8
|
+
const info = await getGitlabProjectInfo(api);
|
|
9
|
+
await api.Projects.edit(info.id, {
|
|
10
|
+
topics: [catladderTopic, ...info.topics],
|
|
11
|
+
});
|
|
17
12
|
};
|
package/src/gitlab/apiClient.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Gitlab } from "@gitbeaker/rest";
|
|
2
2
|
import { getGitlabToken } from "./gitlabToken";
|
|
3
3
|
|
|
4
|
+
export type GitlabRestClient = InstanceType<typeof Gitlab<false>>;
|
|
5
|
+
|
|
4
6
|
export const gitlabApiClient = async ({
|
|
5
7
|
gitlabHost,
|
|
6
8
|
}: {
|
|
7
9
|
gitlabHost?: string;
|
|
8
|
-
} = {}) => {
|
|
10
|
+
} = {}): Promise<GitlabRestClient> => {
|
|
9
11
|
const { token, gitRemoteHost } = await getGitlabToken(gitlabHost);
|
|
10
12
|
if (typeof token !== "string") {
|
|
11
13
|
throw new Error("Gitlab personal access token is missing!");
|
|
@@ -15,6 +17,7 @@ export const gitlabApiClient = async ({
|
|
|
15
17
|
}
|
|
16
18
|
return new Gitlab({
|
|
17
19
|
token,
|
|
18
|
-
host: gitRemoteHost
|
|
20
|
+
host: `https://${gitRemoteHost}`,
|
|
21
|
+
camelize: false,
|
|
19
22
|
});
|
|
20
23
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ProjectSchema } from "@gitbeaker/rest";
|
|
2
|
+
import { getGitRemoteHostAndPath } from "../git/getGitRemoteHostAndPath";
|
|
3
|
+
import type { GitlabRestClient } from "./apiClient";
|
|
4
|
+
import { gitlabApiClient } from "./apiClient";
|
|
5
|
+
import {
|
|
6
|
+
upsertProjectPerferences,
|
|
7
|
+
getProjectPreferences,
|
|
8
|
+
} from "../utils/projectPreferences";
|
|
9
|
+
|
|
10
|
+
export const getGitlabProjectInfo = async (
|
|
11
|
+
existingInstance?: GitlabRestClient,
|
|
12
|
+
): Promise<ProjectSchema> => {
|
|
13
|
+
const api = existingInstance ?? (await gitlabApiClient());
|
|
14
|
+
const { gitRemotePath } = await getGitRemoteHostAndPath();
|
|
15
|
+
const projectInfo = await api.Projects.show(gitRemotePath, {
|
|
16
|
+
statistics: false,
|
|
17
|
+
withCustomAttributes: false,
|
|
18
|
+
license: false,
|
|
19
|
+
});
|
|
20
|
+
// write unawaited intentionally
|
|
21
|
+
upsertProjectPerferences({ gitlabProjectId: projectInfo.id });
|
|
22
|
+
return projectInfo;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const getGitlabProjectId = async (): Promise<number> => {
|
|
26
|
+
const { gitlabProjectId } = await getProjectPreferences();
|
|
27
|
+
if (gitlabProjectId) {
|
|
28
|
+
return gitlabProjectId;
|
|
29
|
+
}
|
|
30
|
+
const projectInfo = await getGitlabProjectInfo();
|
|
31
|
+
return projectInfo.id;
|
|
32
|
+
};
|
package/src/utils/gitlab.ts
CHANGED
|
@@ -5,7 +5,16 @@ import memoizee from "memoizee";
|
|
|
5
5
|
import fetch from "node-fetch";
|
|
6
6
|
import open from "open";
|
|
7
7
|
import type { CommandInstance } from "vorpal";
|
|
8
|
-
import {
|
|
8
|
+
import { hasPreference } from "./preferences";
|
|
9
|
+
import {
|
|
10
|
+
setGitlabToken,
|
|
11
|
+
getGitlabToken as getGitlabTokenFromPerferences,
|
|
12
|
+
getGitlabProjectId,
|
|
13
|
+
getGitlabProjectInfo,
|
|
14
|
+
gitlabApiClient,
|
|
15
|
+
type GitlabRestClient,
|
|
16
|
+
} from "../gitlab";
|
|
17
|
+
import type { Camelize, ProjectVariableSchema } from "@gitbeaker/rest";
|
|
9
18
|
|
|
10
19
|
export const hasGitlabToken = async () =>
|
|
11
20
|
await hasPreference("gitlab-personal-access-token");
|
|
@@ -23,6 +32,10 @@ export const setupGitlabToken = async (vorpal: CommandInstance) => {
|
|
|
23
32
|
name: "shouldContinue",
|
|
24
33
|
type: "prompt",
|
|
25
34
|
});
|
|
35
|
+
if (!shouldContinue) {
|
|
36
|
+
console.info("Aborted by user");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
open("https://git.panter.ch/-/profile/personal_access_tokens");
|
|
28
41
|
|
|
@@ -35,13 +48,14 @@ export const setupGitlabToken = async (vorpal: CommandInstance) => {
|
|
|
35
48
|
message: "Your personal access token ",
|
|
36
49
|
});
|
|
37
50
|
if (typeof personalToken === "string" && personalToken.length) {
|
|
38
|
-
await
|
|
51
|
+
await setGitlabToken(personalToken);
|
|
39
52
|
} else {
|
|
40
53
|
console.error("⚠️ no gitlab personal access token set");
|
|
41
54
|
}
|
|
42
55
|
};
|
|
43
56
|
export const getGitlabToken = async (vorpal: CommandInstance | null) => {
|
|
44
|
-
|
|
57
|
+
const { token } = await getGitlabTokenFromPerferences();
|
|
58
|
+
if (!token?.length) {
|
|
45
59
|
if (!vorpal) {
|
|
46
60
|
console.error(
|
|
47
61
|
"⚠️ gitlab token missing, please run catladder to set it up",
|
|
@@ -50,7 +64,7 @@ export const getGitlabToken = async (vorpal: CommandInstance | null) => {
|
|
|
50
64
|
}
|
|
51
65
|
await setupGitlabToken(vorpal);
|
|
52
66
|
}
|
|
53
|
-
return
|
|
67
|
+
return token;
|
|
54
68
|
};
|
|
55
69
|
|
|
56
70
|
type Method = "GET" | "PUT" | "POST" | "DELETE";
|
|
@@ -93,22 +107,8 @@ export const doGitlabRequest = async <T = any>(
|
|
|
93
107
|
);
|
|
94
108
|
};
|
|
95
109
|
|
|
96
|
-
export const getProjectInfo = async (
|
|
97
|
-
|
|
98
|
-
): Promise<{ id: string; web_url: string }> => {
|
|
99
|
-
const gitRemoteOriginUrl = (
|
|
100
|
-
await exec("git config --get remote.origin.url")
|
|
101
|
-
).stdout.trim();
|
|
102
|
-
const projectPath =
|
|
103
|
-
/(https:\/\/|git@)git\.panter\.ch[:/]([^.]*)(\.git)?/g.exec(
|
|
104
|
-
gitRemoteOriginUrl,
|
|
105
|
-
);
|
|
106
|
-
const project = await doGitlabRequest(
|
|
107
|
-
vorpal,
|
|
108
|
-
`projects/${encodeURIComponent(projectPath[2])}`,
|
|
109
|
-
);
|
|
110
|
-
return project;
|
|
111
|
-
};
|
|
110
|
+
export const getProjectInfo = async (vorpal: CommandInstance | null) =>
|
|
111
|
+
getGitlabProjectInfo();
|
|
112
112
|
|
|
113
113
|
export type GitlabVariable = {
|
|
114
114
|
variable_type: string;
|
|
@@ -121,25 +121,36 @@ export type GitlabVariable = {
|
|
|
121
121
|
export const getAllVariables = memoizee(
|
|
122
122
|
async (
|
|
123
123
|
vorpal: CommandInstance | null,
|
|
124
|
-
|
|
125
|
-
): Promise<
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
preinit?: [number, GitlabRestClient],
|
|
125
|
+
): Promise<(ProjectVariableSchema | Camelize<ProjectVariableSchema>)[]> => {
|
|
126
|
+
const [id, gitlabApi] =
|
|
127
|
+
preinit ?? (await Promise.all([getGitlabProjectId(), gitlabApiClient()]));
|
|
128
|
+
// how many requests to do in parallel, 5 seems a good value for many projects, ideally we would do one request in parallel per component
|
|
129
|
+
const n = 5;
|
|
130
|
+
let all: (ProjectVariableSchema | Camelize<ProjectVariableSchema>)[] = [];
|
|
131
|
+
let result: (ProjectVariableSchema | Camelize<ProjectVariableSchema>)[][] =
|
|
132
|
+
[];
|
|
130
133
|
let page = 1;
|
|
131
134
|
|
|
132
135
|
do {
|
|
133
136
|
// Create an array of promises for N pages
|
|
134
137
|
const promises = Array.from({ length: n }, (_, i) => {
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
);
|
|
138
|
+
return gitlabApi.ProjectVariables.all(id, {
|
|
139
|
+
page: page + i,
|
|
140
|
+
perPage: 100,
|
|
141
|
+
});
|
|
139
142
|
});
|
|
140
143
|
|
|
141
144
|
// Wait for all promises to resolve
|
|
142
|
-
result = await Promise.all(
|
|
145
|
+
result = await Promise.all(
|
|
146
|
+
Array.from({ length: n }, async (_, i) => {
|
|
147
|
+
const res = await gitlabApi.ProjectVariables.all(id, {
|
|
148
|
+
page: page + i,
|
|
149
|
+
perPage: 100,
|
|
150
|
+
});
|
|
151
|
+
return res;
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
143
154
|
|
|
144
155
|
// Increment the page by N
|
|
145
156
|
page += n;
|
|
@@ -165,60 +176,11 @@ export const getVariableValueByRawName = async (
|
|
|
165
176
|
const maskableRegex = new RegExp("^[a-zA-Z0-9_+=/@:.~-]{8,}$"); // SEE https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js#L20
|
|
166
177
|
const isMaskable = (value: string): boolean => maskableRegex.test(value);
|
|
167
178
|
|
|
168
|
-
const
|
|
169
|
-
vorpal: CommandInstance,
|
|
170
|
-
projectId: string,
|
|
171
|
-
key: string,
|
|
172
|
-
value: string,
|
|
173
|
-
masked = true,
|
|
174
|
-
environment_scope = "*",
|
|
175
|
-
) => {
|
|
176
|
-
return await doGitlabRequest(
|
|
177
|
-
vorpal,
|
|
178
|
-
`projects/${projectId}/variables`,
|
|
179
|
-
{
|
|
180
|
-
key,
|
|
181
|
-
value,
|
|
182
|
-
masked: masked && isMaskable(value),
|
|
183
|
-
environment_scope,
|
|
184
|
-
},
|
|
185
|
-
"POST",
|
|
186
|
-
);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const updateVariable = async (
|
|
179
|
+
const getAllCatladderEnvVarsInGitlab = async (
|
|
190
180
|
vorpal: CommandInstance,
|
|
191
|
-
|
|
192
|
-
key: string,
|
|
193
|
-
value: string,
|
|
194
|
-
masked = true,
|
|
181
|
+
preinit?: [number, GitlabRestClient],
|
|
195
182
|
) => {
|
|
196
|
-
|
|
197
|
-
vorpal,
|
|
198
|
-
`projects/${projectId}/variables/${key}`,
|
|
199
|
-
{
|
|
200
|
-
value,
|
|
201
|
-
masked: masked && isMaskable(value),
|
|
202
|
-
},
|
|
203
|
-
"PUT",
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const deleteVariable = async (
|
|
208
|
-
vorpal: CommandInstance,
|
|
209
|
-
projectId: string,
|
|
210
|
-
key: string,
|
|
211
|
-
) => {
|
|
212
|
-
return await doGitlabRequest(
|
|
213
|
-
vorpal,
|
|
214
|
-
`projects/${projectId}/variables/${key}`,
|
|
215
|
-
undefined,
|
|
216
|
-
"DELETE",
|
|
217
|
-
);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const getAllCatladderEnvVarsInGitlab = async (vorpal: CommandInstance) => {
|
|
221
|
-
const allVariables = await getAllVariables(vorpal).then((v) =>
|
|
183
|
+
const allVariables = await getAllVariables(vorpal, preinit).then((v) =>
|
|
222
184
|
v.reduce<{
|
|
223
185
|
[key: string]: {
|
|
224
186
|
value?: string;
|
|
@@ -263,15 +225,23 @@ const getBackupKey = (fullKey: string, timestamp: number) =>
|
|
|
263
225
|
`${fullKey}_backup_${timestamp}`;
|
|
264
226
|
|
|
265
227
|
export const clearBackups = async (vorpal: CommandInstance, keep: number) => {
|
|
266
|
-
const
|
|
267
|
-
|
|
228
|
+
const [id, gitlabApi] = await Promise.all([
|
|
229
|
+
getGitlabProjectId(),
|
|
230
|
+
gitlabApiClient(),
|
|
231
|
+
]);
|
|
232
|
+
const existingVariables = await getAllCatladderEnvVarsInGitlab(vorpal, [
|
|
233
|
+
id,
|
|
234
|
+
gitlabApi,
|
|
235
|
+
]);
|
|
236
|
+
// const { id } = await getProjectInfo(vorpal);
|
|
268
237
|
for (const [key, { backups }] of Object.entries(existingVariables)) {
|
|
269
238
|
const backupsSorted = backups.sort((a, b) => b - a);
|
|
270
239
|
//const toKeep = backupsSorted.slice(0, keep);
|
|
271
240
|
const toDelete = backupsSorted.slice(keep);
|
|
272
241
|
|
|
273
242
|
for (const timestamp of toDelete) {
|
|
274
|
-
await
|
|
243
|
+
await gitlabApi.ProjectVariables.remove(id, getBackupKey(key, timestamp));
|
|
244
|
+
// await deleteVariable(vorpal, id, getBackupKey(key, timestamp));
|
|
275
245
|
}
|
|
276
246
|
}
|
|
277
247
|
};
|
|
@@ -281,13 +251,26 @@ export const upsertAllVariables = async (
|
|
|
281
251
|
variables: Record<string, any>,
|
|
282
252
|
env: string,
|
|
283
253
|
componentName: string,
|
|
284
|
-
|
|
285
|
-
|
|
254
|
+
{
|
|
255
|
+
backup = true,
|
|
256
|
+
masked = true,
|
|
257
|
+
preinit,
|
|
258
|
+
}: {
|
|
259
|
+
backup?: boolean;
|
|
260
|
+
// FIXME: would be better to have this per variable
|
|
261
|
+
masked?: boolean;
|
|
262
|
+
preinit?: [number, GitlabRestClient];
|
|
263
|
+
} = {},
|
|
286
264
|
): Promise<void> => {
|
|
287
|
-
const
|
|
265
|
+
const [id, gitlabApi] =
|
|
266
|
+
preinit ?? (await Promise.all([getGitlabProjectId(), gitlabApiClient()]));
|
|
267
|
+
// const { id } = await getProjectInfo(vorpal);
|
|
288
268
|
|
|
289
269
|
// we list all existing variables. We also gather backup versions, but its currently unused
|
|
290
|
-
const existingVariables = await getAllCatladderEnvVarsInGitlab(vorpal
|
|
270
|
+
const existingVariables = await getAllCatladderEnvVarsInGitlab(vorpal, [
|
|
271
|
+
id,
|
|
272
|
+
gitlabApi,
|
|
273
|
+
]);
|
|
291
274
|
for (const [key, value] of Object.entries(variables ?? {})) {
|
|
292
275
|
const fullKey = getSecretVarName(env, componentName, key);
|
|
293
276
|
const valueSanitized = isObject(value) ? JSON.stringify(value) : `${value}`;
|
|
@@ -299,21 +282,30 @@ export const upsertAllVariables = async (
|
|
|
299
282
|
if (exists) {
|
|
300
283
|
vorpal.log(`changed: ${key}`);
|
|
301
284
|
|
|
302
|
-
await
|
|
285
|
+
await gitlabApi.ProjectVariables.edit(id, fullKey, valueSanitized, {
|
|
286
|
+
filter: { environment_scope: "All (default)" },
|
|
287
|
+
masked,
|
|
288
|
+
});
|
|
289
|
+
// await updateVariable(vorpal, id, fullKey, valueSanitized, masked);
|
|
303
290
|
// write backup
|
|
304
291
|
if (backup) {
|
|
305
|
-
await
|
|
306
|
-
vorpal,
|
|
292
|
+
await gitlabApi.ProjectVariables.create(
|
|
307
293
|
id,
|
|
308
294
|
getBackupKey(fullKey, new Date().getTime()),
|
|
309
295
|
oldValue,
|
|
310
|
-
|
|
311
|
-
|
|
296
|
+
{
|
|
297
|
+
environmentScope: "_backup",
|
|
298
|
+
masked,
|
|
299
|
+
},
|
|
312
300
|
);
|
|
313
301
|
}
|
|
314
302
|
} else {
|
|
315
303
|
vorpal.log(`new : ${key}`);
|
|
316
|
-
await
|
|
304
|
+
await gitlabApi.ProjectVariables.create(id, fullKey, valueSanitized, {
|
|
305
|
+
environmentScope: "All (default)",
|
|
306
|
+
masked,
|
|
307
|
+
});
|
|
308
|
+
// await createVariable(vorpal, id, fullKey, valueSanitized, masked);
|
|
317
309
|
}
|
|
318
310
|
} else {
|
|
319
311
|
vorpal.log(`skip : ${key}`);
|
package/src/utils/preferences.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeFile, readFile, mkdir } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
2
3
|
import { homedir } from "os";
|
|
3
|
-
|
|
4
4
|
import { parse, stringify } from "yaml";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
const directory = join(homedir(), ".catladder");
|
|
8
|
+
const file = join(directory, "preferences.yml");
|
|
7
9
|
|
|
8
10
|
export type CatladderCliPreferences = {
|
|
9
11
|
"gitlab-personal-access-token": string | null;
|
|
@@ -15,7 +17,7 @@ const perfKeys = [
|
|
|
15
17
|
"gitlab-personal-access-token",
|
|
16
18
|
"googleAuthUserNumber",
|
|
17
19
|
"gitlabTokens",
|
|
18
|
-
] as const;
|
|
20
|
+
] as const satisfies readonly PreferenceKey[];
|
|
19
21
|
|
|
20
22
|
const fallbackValues = {
|
|
21
23
|
"gitlab-personal-access-token": null,
|
|
@@ -23,23 +25,47 @@ const fallbackValues = {
|
|
|
23
25
|
googleAuthUserNumber: null,
|
|
24
26
|
} satisfies CatladderCliPreferences;
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const fileString = await readFile(file, { encoding: "utf-8" });
|
|
31
|
-
const fileParsed = parse(fileString || "---\nnull") ?? {};
|
|
32
|
-
return perfKeys.reduce(
|
|
28
|
+
const perferencesWithParsed = (
|
|
29
|
+
parsed: Partial<CatladderCliPreferences>,
|
|
30
|
+
): CatladderCliPreferences =>
|
|
31
|
+
perfKeys.reduce(
|
|
33
32
|
(acc, key) =>
|
|
34
|
-
Object.assign(acc, { [key]:
|
|
33
|
+
Object.assign(acc, { [key]: parsed[key] ?? fallbackValues[key] }),
|
|
35
34
|
{} as CatladderCliPreferences,
|
|
36
35
|
);
|
|
36
|
+
|
|
37
|
+
const encoding = "utf-8";
|
|
38
|
+
|
|
39
|
+
const readPreferences = () => readFile(file, { encoding });
|
|
40
|
+
|
|
41
|
+
const writePreferences = (preferences: CatladderCliPreferences) =>
|
|
42
|
+
writeFile(file, stringify(preferences), { encoding, mode: 0o600 });
|
|
43
|
+
|
|
44
|
+
const ensureDir = async () =>
|
|
45
|
+
existsSync(directory)
|
|
46
|
+
? directory
|
|
47
|
+
: mkdir(directory, { recursive: true, mode: 0o700 });
|
|
48
|
+
|
|
49
|
+
const ensureFile = async () => {
|
|
50
|
+
if (existsSync(file)) {
|
|
51
|
+
return file;
|
|
52
|
+
}
|
|
53
|
+
await ensureDir();
|
|
54
|
+
return writePreferences(perferencesWithParsed({}));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getPreferences = async (): Promise<CatladderCliPreferences> => {
|
|
58
|
+
await ensureFile();
|
|
59
|
+
const fileString = await readPreferences();
|
|
60
|
+
const fileParsed = parse(fileString || "---\nnull") ?? {};
|
|
61
|
+
return perferencesWithParsed(fileParsed);
|
|
37
62
|
};
|
|
38
63
|
|
|
39
64
|
export const hasPreference = async (key: PreferenceKey) => {
|
|
40
65
|
const preferences = await getPreferences();
|
|
41
66
|
return preferences[key] !== null && preferences[key] !== undefined;
|
|
42
67
|
};
|
|
68
|
+
|
|
43
69
|
export const getPreference = async <K extends PreferenceKey>(
|
|
44
70
|
key: K,
|
|
45
71
|
): Promise<CatladderCliPreferences[K] | null> => {
|
|
@@ -55,11 +81,5 @@ export const setPreference = async (
|
|
|
55
81
|
value: CatladderCliPreferences[typeof key],
|
|
56
82
|
) => {
|
|
57
83
|
const preferences = await getPreferences();
|
|
58
|
-
|
|
59
|
-
const newPreferences = {
|
|
60
|
-
...preferences,
|
|
61
|
-
[key]: value,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
await writeFile(file, stringify(newPreferences));
|
|
84
|
+
return writePreferences({ ...preferences, [key]: value });
|
|
65
85
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { writeFile, readFile, mkdir } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getProjectRootPath } from "../git";
|
|
5
|
+
|
|
6
|
+
const directory = ".catladder";
|
|
7
|
+
const file = "preferences.json";
|
|
8
|
+
|
|
9
|
+
export type CatladderCliProjectPreferences = {
|
|
10
|
+
gitlabProjectId?: number | null;
|
|
11
|
+
};
|
|
12
|
+
type ProjectPreferenceKey = keyof CatladderCliProjectPreferences;
|
|
13
|
+
const perfKeys = [
|
|
14
|
+
"gitlabProjectId",
|
|
15
|
+
] as const satisfies readonly ProjectPreferenceKey[];
|
|
16
|
+
const fallbackValues = {
|
|
17
|
+
gitlabProjectId: null,
|
|
18
|
+
} satisfies CatladderCliProjectPreferences;
|
|
19
|
+
|
|
20
|
+
const perferencesWithParsed = (
|
|
21
|
+
parsed: Partial<CatladderCliProjectPreferences>,
|
|
22
|
+
): CatladderCliProjectPreferences =>
|
|
23
|
+
perfKeys.reduce(
|
|
24
|
+
(acc, key) =>
|
|
25
|
+
Object.assign(acc, { [key]: parsed[key] ?? fallbackValues[key] ?? null }),
|
|
26
|
+
{} as CatladderCliProjectPreferences,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const encoding = "utf-8";
|
|
30
|
+
|
|
31
|
+
const readPreferences = (filePath: string) => readFile(filePath, { encoding });
|
|
32
|
+
|
|
33
|
+
const writePreferences = (
|
|
34
|
+
filePath: string,
|
|
35
|
+
preferences: CatladderCliProjectPreferences,
|
|
36
|
+
) =>
|
|
37
|
+
writeFile(filePath, JSON.stringify(preferences, null, 2), {
|
|
38
|
+
encoding,
|
|
39
|
+
mode: 0o600,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const ensureDir = async (dirPath: string) =>
|
|
43
|
+
existsSync(dirPath)
|
|
44
|
+
? dirPath
|
|
45
|
+
: mkdir(dirPath, { recursive: true, mode: 0o700 });
|
|
46
|
+
|
|
47
|
+
const ensureFile = async (givenFilePath?: string) => {
|
|
48
|
+
if (givenFilePath?.length && existsSync(givenFilePath)) {
|
|
49
|
+
return givenFilePath;
|
|
50
|
+
}
|
|
51
|
+
const rootPath = await getProjectRootPath();
|
|
52
|
+
const dirPath = join(rootPath, directory);
|
|
53
|
+
const filePath = join(dirPath, file);
|
|
54
|
+
if (existsSync(filePath)) {
|
|
55
|
+
return filePath;
|
|
56
|
+
}
|
|
57
|
+
await ensureDir(dirPath);
|
|
58
|
+
await writePreferences(filePath, perferencesWithParsed({}));
|
|
59
|
+
return filePath;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const getProjectPreferences = async (
|
|
63
|
+
givenFilePath?: string,
|
|
64
|
+
): Promise<CatladderCliProjectPreferences> => {
|
|
65
|
+
const filePath = await ensureFile(givenFilePath);
|
|
66
|
+
const fileString = await readPreferences(filePath);
|
|
67
|
+
const fileParsed = JSON.parse(fileString || "null") ?? {};
|
|
68
|
+
return perferencesWithParsed(fileParsed);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const upsertProjectPerferences = async (
|
|
72
|
+
newPerfs: Partial<CatladderCliProjectPreferences> = {},
|
|
73
|
+
givenFilePath?: string,
|
|
74
|
+
) => {
|
|
75
|
+
const filePath = await ensureFile(givenFilePath);
|
|
76
|
+
const currentPerfs = await getProjectPreferences(filePath);
|
|
77
|
+
return writePreferences(filePath, { ...currentPerfs, ...newPerfs });
|
|
78
|
+
};
|