@credibledata/cred-cli 0.1.3 → 0.1.5
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/.prettierignore +2 -1
- package/README.md +0 -2
- package/bun.lock +182 -283
- package/dist/client/api.js +804 -111
- package/dist/client/api.js.map +1 -1
- package/dist/clientconfiguration.js +55 -13
- package/dist/clientconfiguration.js.map +1 -1
- package/dist/clientconfiguration.spec.js +305 -16
- package/dist/clientconfiguration.spec.js.map +1 -1
- package/dist/commands/add.js +6 -3
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/connection/createconnections.js +18 -3
- package/dist/commands/connection/createconnections.js.map +1 -1
- package/dist/commands/connection/createconnections.spec.js +219 -0
- package/dist/commands/connection/createconnections.spec.js.map +1 -0
- package/dist/commands/connection/deleteconnection.spec.js +130 -0
- package/dist/commands/connection/deleteconnection.spec.js.map +1 -0
- package/dist/commands/connection/listconnections.js +9 -3
- package/dist/commands/connection/listconnections.js.map +1 -1
- package/dist/commands/connection/listconnections.spec.js +117 -0
- package/dist/commands/connection/listconnections.spec.js.map +1 -0
- package/dist/commands/group/addmember.js +2 -0
- package/dist/commands/group/addmember.js.map +1 -1
- package/dist/commands/group/addmember.spec.js +148 -0
- package/dist/commands/group/addmember.spec.js.map +1 -0
- package/dist/commands/group/creategroup.spec.js +122 -0
- package/dist/commands/group/creategroup.spec.js.map +1 -0
- package/dist/commands/group/deletegroup.spec.js +104 -0
- package/dist/commands/group/deletegroup.spec.js.map +1 -0
- package/dist/commands/group/getgroup.spec.js +111 -0
- package/dist/commands/group/getgroup.spec.js.map +1 -0
- package/dist/commands/group/listgroups.spec.js +118 -0
- package/dist/commands/group/listgroups.spec.js.map +1 -0
- package/dist/commands/group/removemember.js +1 -0
- package/dist/commands/group/removemember.js.map +1 -1
- package/dist/commands/group/removemember.spec.js +138 -0
- package/dist/commands/group/removemember.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/createToken.js +6 -2
- package/dist/commands/groupaccesstoken/createToken.js.map +1 -1
- package/dist/commands/groupaccesstoken/createToken.spec.js +127 -0
- package/dist/commands/groupaccesstoken/createToken.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/deleteToken.js.map +1 -1
- package/dist/commands/groupaccesstoken/deleteToken.spec.js +103 -0
- package/dist/commands/groupaccesstoken/deleteToken.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/groupContainer.js.map +1 -1
- package/dist/commands/groupaccesstoken/listTokens.js.map +1 -1
- package/dist/commands/groupaccesstoken/listTokens.spec.js +115 -0
- package/dist/commands/groupaccesstoken/listTokens.spec.js.map +1 -0
- package/dist/commands/list.js +10 -3
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/misc/login.js +146 -22
- package/dist/commands/misc/login.js.map +1 -1
- package/dist/commands/misc/login.spec.js +111 -6
- package/dist/commands/misc/login.spec.js.map +1 -1
- package/dist/commands/misc/logout.spec.js +30 -0
- package/dist/commands/misc/logout.spec.js.map +1 -0
- package/dist/commands/misc/loopbackOAuth.js +110 -0
- package/dist/commands/misc/loopbackOAuth.js.map +1 -0
- package/dist/commands/misc/set-access-token.js.map +1 -1
- package/dist/commands/misc/set-access-token.spec.js +65 -0
- package/dist/commands/misc/set-access-token.spec.js.map +1 -0
- package/dist/commands/package/deletepackage.spec.js +136 -0
- package/dist/commands/package/deletepackage.spec.js.map +1 -0
- package/dist/commands/package/listpackages.js +10 -5
- package/dist/commands/package/listpackages.js.map +1 -1
- package/dist/commands/package/listpackages.spec.js +129 -0
- package/dist/commands/package/listpackages.spec.js.map +1 -0
- package/dist/commands/package/publish.js +23 -0
- package/dist/commands/package/publish.js.map +1 -1
- package/dist/commands/package/publish.spec.js +511 -0
- package/dist/commands/package/publish.spec.js.map +1 -0
- package/dist/commands/package/updatepackage.spec.js +50 -0
- package/dist/commands/package/updatepackage.spec.js.map +1 -1
- package/dist/commands/project/createproject.js +11 -1
- package/dist/commands/project/createproject.js.map +1 -1
- package/dist/commands/project/createproject.spec.js +154 -0
- package/dist/commands/project/createproject.spec.js.map +1 -0
- package/dist/commands/project/deleteproject.spec.js +106 -0
- package/dist/commands/project/deleteproject.spec.js.map +1 -0
- package/dist/commands/project/getproject.spec.js +99 -0
- package/dist/commands/project/getproject.spec.js.map +1 -0
- package/dist/commands/project/listprojects.js +13 -7
- package/dist/commands/project/listprojects.js.map +1 -1
- package/dist/commands/project/listprojects.spec.js +111 -0
- package/dist/commands/project/listprojects.spec.js.map +1 -0
- package/dist/commands/project/setproject.spec.js +58 -0
- package/dist/commands/project/setproject.spec.js.map +1 -0
- package/dist/commands/project/updateproject.js +68 -0
- package/dist/commands/project/updateproject.js.map +1 -0
- package/dist/commands/project/updateproject.spec.js +129 -0
- package/dist/commands/project/updateproject.spec.js.map +1 -0
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/update.js +13 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/version/listversions.spec.js +121 -0
- package/dist/commands/version/listversions.spec.js.map +1 -0
- package/dist/common/UserGroupId.spec.js +92 -0
- package/dist/common/UserGroupId.spec.js.map +1 -0
- package/dist/constants.js +49 -13
- package/dist/constants.js.map +1 -1
- package/dist/index.js +40 -18
- package/dist/index.js.map +1 -1
- package/dist/logger.spec.js +125 -0
- package/dist/logger.spec.js.map +1 -0
- package/dist/polyfills/file.polyfill.js.map +1 -1
- package/dist/utils.js +59 -0
- package/dist/utils.js.map +1 -1
- package/dist/utils.spec.js +158 -0
- package/dist/utils.spec.js.map +1 -0
- package/docker/Dockerfile.ci +11 -3
- package/eslint.config.mjs +45 -0
- package/generate-client.sh +5 -0
- package/package.json +24 -16
- package/src/client/.openapi-generator/FILES +1 -0
- package/src/client/api.ts +1999 -696
- package/src/clientconfiguration.spec.ts +394 -20
- package/src/clientconfiguration.ts +74 -15
- package/src/commands/add.ts +15 -6
- package/src/commands/connection/createconnections.spec.ts +403 -0
- package/src/commands/connection/createconnections.ts +30 -5
- package/src/commands/connection/deleteconnection.spec.ts +189 -0
- package/src/commands/connection/listconnections.spec.ts +132 -0
- package/src/commands/connection/listconnections.ts +22 -3
- package/src/commands/group/addmember.spec.ts +257 -0
- package/src/commands/group/addmember.ts +2 -0
- package/src/commands/group/creategroup.spec.ts +146 -0
- package/src/commands/group/deletegroup.spec.ts +120 -0
- package/src/commands/group/getgroup.spec.ts +129 -0
- package/src/commands/group/listgroups.spec.ts +137 -0
- package/src/commands/group/removemember.spec.ts +224 -0
- package/src/commands/group/removemember.ts +1 -0
- package/src/commands/groupaccesstoken/createToken.spec.ts +211 -0
- package/src/commands/groupaccesstoken/createToken.ts +55 -51
- package/src/commands/groupaccesstoken/deleteToken.spec.ts +147 -0
- package/src/commands/groupaccesstoken/deleteToken.ts +16 -11
- package/src/commands/groupaccesstoken/groupContainer.ts +51 -51
- package/src/commands/groupaccesstoken/listTokens.spec.ts +147 -0
- package/src/commands/groupaccesstoken/listTokens.ts +16 -11
- package/src/commands/list.ts +50 -9
- package/src/commands/misc/login.spec.ts +91 -6
- package/src/commands/misc/login.ts +208 -25
- package/src/commands/misc/logout.spec.ts +32 -0
- package/src/commands/misc/loopbackOAuth.ts +127 -0
- package/src/commands/misc/set-access-token.spec.ts +95 -0
- package/src/commands/misc/set-access-token.ts +16 -16
- package/src/commands/package/deletepackage.spec.ts +164 -0
- package/src/commands/package/listpackages.spec.ts +147 -0
- package/src/commands/package/listpackages.ts +28 -5
- package/src/commands/package/publish.spec.ts +597 -0
- package/src/commands/package/publish.ts +32 -0
- package/src/commands/package/updatepackage.spec.ts +98 -0
- package/src/commands/project/createproject.spec.ts +247 -0
- package/src/commands/project/createproject.ts +12 -1
- package/src/commands/project/deleteproject.spec.ts +123 -0
- package/src/commands/project/getproject.spec.ts +109 -0
- package/src/commands/project/listprojects.spec.ts +121 -0
- package/src/commands/project/listprojects.ts +33 -7
- package/src/commands/project/setproject.spec.ts +63 -0
- package/src/commands/project/updateproject.spec.ts +217 -0
- package/src/commands/project/updateproject.ts +82 -0
- package/src/commands/rm.ts +1 -6
- package/src/commands/update.ts +24 -1
- package/src/commands/version/listversions.spec.ts +138 -0
- package/src/common/UserGroupId.spec.ts +117 -0
- package/src/constants.ts +59 -13
- package/src/index.ts +46 -20
- package/src/logger.spec.ts +158 -0
- package/src/polyfills/file.polyfill.ts +1 -0
- package/src/utils.spec.ts +135 -0
- package/src/utils.ts +81 -0
- package/tsconfig.json +15 -18
- package/.eslintrc +0 -33
|
@@ -4,6 +4,19 @@ import os from "os";
|
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import sinon from "sinon";
|
|
6
6
|
import { ClientConfiguration } from "./clientconfiguration";
|
|
7
|
+
import { ALL_ENVIRONMENTS, type Environment } from "./constants";
|
|
8
|
+
import { logger } from "./logger";
|
|
9
|
+
|
|
10
|
+
/** Expected `configPerEnvironment` shape after `loadConfig()` (every env has an object). */
|
|
11
|
+
function fullPerEnv(
|
|
12
|
+
filled: Partial<Record<Environment, Record<string, unknown>>> = {},
|
|
13
|
+
): Record<string, Record<string, unknown>> {
|
|
14
|
+
const out: Record<string, Record<string, unknown>> = {};
|
|
15
|
+
for (const env of ALL_ENVIRONMENTS) {
|
|
16
|
+
out[env] = filled[env] != null ? { ...filled[env]! } : {};
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
7
20
|
|
|
8
21
|
describe("ClientConfiguration", () => {
|
|
9
22
|
let clientConfig: ClientConfiguration;
|
|
@@ -33,11 +46,7 @@ describe("ClientConfiguration", () => {
|
|
|
33
46
|
readFileSyncStub.throws(new Error("File not found"));
|
|
34
47
|
clientConfig.loadConfig();
|
|
35
48
|
expect(clientConfig["config"]).to.deep.equal({
|
|
36
|
-
configPerEnvironment:
|
|
37
|
-
production: {},
|
|
38
|
-
staging: {},
|
|
39
|
-
localhost: {},
|
|
40
|
-
},
|
|
49
|
+
configPerEnvironment: fullPerEnv(),
|
|
41
50
|
activeEnvironment: "production",
|
|
42
51
|
});
|
|
43
52
|
});
|
|
@@ -53,7 +62,33 @@ describe("ClientConfiguration", () => {
|
|
|
53
62
|
};
|
|
54
63
|
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
55
64
|
clientConfig.loadConfig();
|
|
56
|
-
expect(clientConfig["config"]).to.deep.equal(
|
|
65
|
+
expect(clientConfig["config"]).to.deep.equal({
|
|
66
|
+
configPerEnvironment: fullPerEnv({
|
|
67
|
+
production: { project: "test-project" },
|
|
68
|
+
}),
|
|
69
|
+
activeEnvironment: "production",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should allow setAccessToken when activeEnvironment had no bucket on disk (e.g. videoamp)", () => {
|
|
74
|
+
const mockConfig = {
|
|
75
|
+
configPerEnvironment: {
|
|
76
|
+
production: {},
|
|
77
|
+
staging: {},
|
|
78
|
+
dev: {},
|
|
79
|
+
localhost: {},
|
|
80
|
+
"e2e-test": {},
|
|
81
|
+
},
|
|
82
|
+
activeEnvironment: "videoamp",
|
|
83
|
+
};
|
|
84
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
85
|
+
clientConfig.setAccessToken(true, "user-jwt");
|
|
86
|
+
const written = JSON.parse(writeFileSyncStub.lastCall.args[1] as string);
|
|
87
|
+
expect(written.configPerEnvironment.videoamp.accessToken).to.equal(
|
|
88
|
+
"user-jwt",
|
|
89
|
+
);
|
|
90
|
+
expect(written.configPerEnvironment.videoamp.isServiceAccountToken).to.be
|
|
91
|
+
.false;
|
|
57
92
|
});
|
|
58
93
|
|
|
59
94
|
it("should set and get the project correctly", () => {
|
|
@@ -64,11 +99,9 @@ describe("ClientConfiguration", () => {
|
|
|
64
99
|
writeFileSyncStub.calledWith(
|
|
65
100
|
customConfigPath,
|
|
66
101
|
JSON.stringify({
|
|
67
|
-
configPerEnvironment: {
|
|
102
|
+
configPerEnvironment: fullPerEnv({
|
|
68
103
|
production: { project: "new-project" },
|
|
69
|
-
|
|
70
|
-
localhost: {},
|
|
71
|
-
},
|
|
104
|
+
}),
|
|
72
105
|
activeEnvironment: "production",
|
|
73
106
|
}),
|
|
74
107
|
),
|
|
@@ -83,11 +116,12 @@ describe("ClientConfiguration", () => {
|
|
|
83
116
|
writeFileSyncStub.calledWith(
|
|
84
117
|
customConfigPath,
|
|
85
118
|
JSON.stringify({
|
|
86
|
-
configPerEnvironment: {
|
|
87
|
-
production: {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
configPerEnvironment: fullPerEnv({
|
|
120
|
+
production: {
|
|
121
|
+
isServiceAccountToken: true,
|
|
122
|
+
accessToken: "token-123",
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
91
125
|
activeEnvironment: "production",
|
|
92
126
|
}),
|
|
93
127
|
),
|
|
@@ -103,14 +137,354 @@ describe("ClientConfiguration", () => {
|
|
|
103
137
|
writeFileSyncStub.calledWith(
|
|
104
138
|
customConfigPath,
|
|
105
139
|
JSON.stringify({
|
|
106
|
-
configPerEnvironment: {
|
|
107
|
-
production: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
configPerEnvironment: fullPerEnv({
|
|
141
|
+
production: {
|
|
142
|
+
isServiceAccountToken: false,
|
|
143
|
+
accessToken: "new-token",
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
activeEnvironment: "production",
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
149
|
+
).to.be.true;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should set and get organization", () => {
|
|
153
|
+
readFileSyncStub.throws(new Error("File not found"));
|
|
154
|
+
clientConfig.setOrganization("my-org");
|
|
155
|
+
|
|
156
|
+
expect(
|
|
157
|
+
writeFileSyncStub.calledWith(
|
|
158
|
+
customConfigPath,
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
configPerEnvironment: fullPerEnv({
|
|
161
|
+
production: { organization: "my-org" },
|
|
162
|
+
}),
|
|
163
|
+
activeEnvironment: "production",
|
|
164
|
+
}),
|
|
165
|
+
),
|
|
166
|
+
).to.be.true;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should set and get refresh token", () => {
|
|
170
|
+
readFileSyncStub.throws(new Error("File not found"));
|
|
171
|
+
clientConfig.setRefreshToken("refresh-token-123");
|
|
172
|
+
|
|
173
|
+
expect(
|
|
174
|
+
writeFileSyncStub.calledWith(
|
|
175
|
+
customConfigPath,
|
|
176
|
+
JSON.stringify({
|
|
177
|
+
configPerEnvironment: fullPerEnv({
|
|
178
|
+
production: { refreshToken: "refresh-token-123" },
|
|
179
|
+
}),
|
|
180
|
+
activeEnvironment: "production",
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
).to.be.true;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should set and get VSCode user", () => {
|
|
187
|
+
readFileSyncStub.throws(new Error("File not found"));
|
|
188
|
+
clientConfig.setVSCodeUser("testuser", "test@example.com");
|
|
189
|
+
|
|
190
|
+
expect(
|
|
191
|
+
writeFileSyncStub.calledWith(
|
|
192
|
+
customConfigPath,
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
configPerEnvironment: fullPerEnv({
|
|
195
|
+
production: {
|
|
196
|
+
vsCodeUser: {
|
|
197
|
+
username: "testuser",
|
|
198
|
+
email: "test@example.com",
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
111
202
|
activeEnvironment: "production",
|
|
112
203
|
}),
|
|
113
204
|
),
|
|
114
205
|
).to.be.true;
|
|
115
206
|
});
|
|
207
|
+
|
|
208
|
+
it("should set and get JWT access token", () => {
|
|
209
|
+
readFileSyncStub.throws(new Error("File not found"));
|
|
210
|
+
clientConfig.setJwtAccessToken("jwt-token-abc");
|
|
211
|
+
|
|
212
|
+
expect(
|
|
213
|
+
writeFileSyncStub.calledWith(
|
|
214
|
+
customConfigPath,
|
|
215
|
+
JSON.stringify({
|
|
216
|
+
configPerEnvironment: fullPerEnv({
|
|
217
|
+
production: { jwtAccessToken: "jwt-token-abc" },
|
|
218
|
+
}),
|
|
219
|
+
activeEnvironment: "production",
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
222
|
+
).to.be.true;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should return true for doesConfigExists when environment config exists", () => {
|
|
226
|
+
const mockConfig = {
|
|
227
|
+
configPerEnvironment: {
|
|
228
|
+
production: { project: "test" },
|
|
229
|
+
staging: {},
|
|
230
|
+
localhost: {},
|
|
231
|
+
},
|
|
232
|
+
activeEnvironment: "production",
|
|
233
|
+
};
|
|
234
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
235
|
+
|
|
236
|
+
expect(clientConfig.doesConfigExists()).to.be.true;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should destroy config by resetting environment config to empty", () => {
|
|
240
|
+
const mockConfig = {
|
|
241
|
+
configPerEnvironment: {
|
|
242
|
+
production: { organization: "my-org", project: "my-proj" },
|
|
243
|
+
staging: {},
|
|
244
|
+
localhost: {},
|
|
245
|
+
},
|
|
246
|
+
activeEnvironment: "production",
|
|
247
|
+
};
|
|
248
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
249
|
+
clientConfig.destroyConfig();
|
|
250
|
+
|
|
251
|
+
const lastWrite = writeFileSyncStub.lastCall.args[1] as string;
|
|
252
|
+
expect(JSON.parse(lastWrite)).to.deep.equal({
|
|
253
|
+
configPerEnvironment: fullPerEnv(),
|
|
254
|
+
activeEnvironment: "production",
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should set and get environment", () => {
|
|
259
|
+
readFileSyncStub.throws(new Error("File not found"));
|
|
260
|
+
clientConfig.setEnvironment("staging");
|
|
261
|
+
|
|
262
|
+
expect(
|
|
263
|
+
writeFileSyncStub.calledWith(
|
|
264
|
+
customConfigPath,
|
|
265
|
+
JSON.stringify({
|
|
266
|
+
configPerEnvironment: fullPerEnv(),
|
|
267
|
+
activeEnvironment: "staging",
|
|
268
|
+
}),
|
|
269
|
+
),
|
|
270
|
+
).to.be.true;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should return headers with Bearer token for user tokens", () => {
|
|
274
|
+
const mockConfig = {
|
|
275
|
+
configPerEnvironment: {
|
|
276
|
+
production: {
|
|
277
|
+
accessToken: "user-token",
|
|
278
|
+
isServiceAccountToken: false,
|
|
279
|
+
},
|
|
280
|
+
staging: {},
|
|
281
|
+
localhost: {},
|
|
282
|
+
},
|
|
283
|
+
activeEnvironment: "production",
|
|
284
|
+
};
|
|
285
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
286
|
+
clientConfig.loadConfig();
|
|
287
|
+
|
|
288
|
+
const headers = clientConfig.getHeaders();
|
|
289
|
+
expect(headers.Authorization).to.equal("Bearer user-token");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should return headers with ApiKey for service account tokens", () => {
|
|
293
|
+
const mockConfig = {
|
|
294
|
+
configPerEnvironment: {
|
|
295
|
+
production: {
|
|
296
|
+
accessToken: "service-token",
|
|
297
|
+
isServiceAccountToken: true,
|
|
298
|
+
},
|
|
299
|
+
staging: {},
|
|
300
|
+
localhost: {},
|
|
301
|
+
},
|
|
302
|
+
activeEnvironment: "production",
|
|
303
|
+
};
|
|
304
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
305
|
+
clientConfig.loadConfig();
|
|
306
|
+
|
|
307
|
+
const headers = clientConfig.getHeaders();
|
|
308
|
+
expect(headers.Authorization).to.equal("ApiKey service-token");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should reset to defaults when config file is corrupted (missing activeEnvironment)", () => {
|
|
312
|
+
readFileSyncStub.returns(JSON.stringify({ configPerEnvironment: {} }));
|
|
313
|
+
clientConfig.loadConfig();
|
|
314
|
+
|
|
315
|
+
expect(clientConfig["config"].activeEnvironment).to.equal("production");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("isTokenValid", () => {
|
|
319
|
+
it("should return invalid when no token exists", () => {
|
|
320
|
+
const mockConfig = {
|
|
321
|
+
configPerEnvironment: {
|
|
322
|
+
production: {},
|
|
323
|
+
staging: {},
|
|
324
|
+
localhost: {},
|
|
325
|
+
},
|
|
326
|
+
activeEnvironment: "production",
|
|
327
|
+
};
|
|
328
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
329
|
+
|
|
330
|
+
const result = clientConfig.isTokenValid();
|
|
331
|
+
expect(result.valid).to.be.false;
|
|
332
|
+
expect(result.reason).to.equal("No token found. Please login.");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should return valid for service account tokens", () => {
|
|
336
|
+
const mockConfig = {
|
|
337
|
+
configPerEnvironment: {
|
|
338
|
+
production: {
|
|
339
|
+
accessToken: "some-api-key",
|
|
340
|
+
isServiceAccountToken: true,
|
|
341
|
+
},
|
|
342
|
+
staging: {},
|
|
343
|
+
localhost: {},
|
|
344
|
+
},
|
|
345
|
+
activeEnvironment: "production",
|
|
346
|
+
};
|
|
347
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
348
|
+
|
|
349
|
+
const result = clientConfig.isTokenValid();
|
|
350
|
+
expect(result.valid).to.be.true;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should return invalid for expired JWT", () => {
|
|
354
|
+
// Create a JWT with an expired exp claim
|
|
355
|
+
const payload = { exp: Math.floor(Date.now() / 1000) - 3600 };
|
|
356
|
+
const fakeJwt =
|
|
357
|
+
Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
|
|
358
|
+
"base64url",
|
|
359
|
+
) +
|
|
360
|
+
"." +
|
|
361
|
+
Buffer.from(JSON.stringify(payload)).toString("base64url") +
|
|
362
|
+
".signature";
|
|
363
|
+
|
|
364
|
+
const mockConfig = {
|
|
365
|
+
configPerEnvironment: {
|
|
366
|
+
production: {
|
|
367
|
+
accessToken: fakeJwt,
|
|
368
|
+
isServiceAccountToken: false,
|
|
369
|
+
},
|
|
370
|
+
staging: {},
|
|
371
|
+
localhost: {},
|
|
372
|
+
},
|
|
373
|
+
activeEnvironment: "production",
|
|
374
|
+
};
|
|
375
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
376
|
+
|
|
377
|
+
const result = clientConfig.isTokenValid();
|
|
378
|
+
expect(result.valid).to.be.false;
|
|
379
|
+
expect(result.reason).to.equal("Token expired. Please login again.");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should return valid for non-expired JWT", () => {
|
|
383
|
+
const payload = { exp: Math.floor(Date.now() / 1000) + 3600 };
|
|
384
|
+
const fakeJwt =
|
|
385
|
+
Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
|
|
386
|
+
"base64url",
|
|
387
|
+
) +
|
|
388
|
+
"." +
|
|
389
|
+
Buffer.from(JSON.stringify(payload)).toString("base64url") +
|
|
390
|
+
".signature";
|
|
391
|
+
|
|
392
|
+
const mockConfig = {
|
|
393
|
+
configPerEnvironment: {
|
|
394
|
+
production: {
|
|
395
|
+
accessToken: fakeJwt,
|
|
396
|
+
isServiceAccountToken: false,
|
|
397
|
+
},
|
|
398
|
+
staging: {},
|
|
399
|
+
localhost: {},
|
|
400
|
+
},
|
|
401
|
+
activeEnvironment: "production",
|
|
402
|
+
};
|
|
403
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
404
|
+
|
|
405
|
+
const result = clientConfig.isTokenValid();
|
|
406
|
+
expect(result.valid).to.be.true;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("should return invalid for malformed token", () => {
|
|
410
|
+
const mockConfig = {
|
|
411
|
+
configPerEnvironment: {
|
|
412
|
+
production: {
|
|
413
|
+
accessToken: "not-a-valid-jwt",
|
|
414
|
+
isServiceAccountToken: false,
|
|
415
|
+
},
|
|
416
|
+
staging: {},
|
|
417
|
+
localhost: {},
|
|
418
|
+
},
|
|
419
|
+
activeEnvironment: "production",
|
|
420
|
+
};
|
|
421
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
422
|
+
|
|
423
|
+
const result = clientConfig.isTokenValid();
|
|
424
|
+
expect(result.valid).to.be.false;
|
|
425
|
+
expect(result.reason).to.equal(
|
|
426
|
+
"Invalid token format. Please login again.",
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe("printStatus", () => {
|
|
432
|
+
let loggerStub: sinon.SinonStubbedInstance<typeof logger>;
|
|
433
|
+
let consoleLogStub: sinon.SinonStub;
|
|
434
|
+
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
loggerStub = sinon.stub(logger);
|
|
437
|
+
consoleLogStub = sinon.stub(console, "log");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("should print org and project when token is valid", () => {
|
|
441
|
+
const payload = { exp: Math.floor(Date.now() / 1000) + 3600 };
|
|
442
|
+
const fakeJwt =
|
|
443
|
+
Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
|
|
444
|
+
"base64url",
|
|
445
|
+
) +
|
|
446
|
+
"." +
|
|
447
|
+
Buffer.from(JSON.stringify(payload)).toString("base64url") +
|
|
448
|
+
".signature";
|
|
449
|
+
|
|
450
|
+
const mockConfig = {
|
|
451
|
+
configPerEnvironment: {
|
|
452
|
+
production: {
|
|
453
|
+
accessToken: fakeJwt,
|
|
454
|
+
isServiceAccountToken: false,
|
|
455
|
+
organization: "my-org",
|
|
456
|
+
project: "my-project",
|
|
457
|
+
},
|
|
458
|
+
staging: {},
|
|
459
|
+
localhost: {},
|
|
460
|
+
},
|
|
461
|
+
activeEnvironment: "production",
|
|
462
|
+
};
|
|
463
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
464
|
+
|
|
465
|
+
clientConfig.printStatus();
|
|
466
|
+
|
|
467
|
+
expect(loggerStub.info.calledWith(sinon.match("Organization:"))).to.be
|
|
468
|
+
.true;
|
|
469
|
+
expect(loggerStub.info.calledWith(sinon.match("Project:"))).to.be.true;
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("should print error when token is invalid", () => {
|
|
473
|
+
const mockConfig = {
|
|
474
|
+
configPerEnvironment: {
|
|
475
|
+
production: {},
|
|
476
|
+
staging: {},
|
|
477
|
+
localhost: {},
|
|
478
|
+
},
|
|
479
|
+
activeEnvironment: "production",
|
|
480
|
+
};
|
|
481
|
+
readFileSyncStub.returns(JSON.stringify(mockConfig));
|
|
482
|
+
|
|
483
|
+
clientConfig.printStatus();
|
|
484
|
+
|
|
485
|
+
expect(
|
|
486
|
+
loggerStub.error.calledWith(sinon.match("Failed to get status")),
|
|
487
|
+
).to.be.true;
|
|
488
|
+
});
|
|
489
|
+
});
|
|
116
490
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
+
import { jwtDecode } from "jwt-decode";
|
|
2
3
|
import kleur from "kleur";
|
|
3
4
|
import os from "os";
|
|
4
|
-
import { ASCII_TITLE, Environment } from "./constants";
|
|
5
|
+
import { ALL_ENVIRONMENTS, ASCII_TITLE, Environment } from "./constants";
|
|
5
6
|
import { logger } from "./logger";
|
|
6
7
|
|
|
7
8
|
interface ConfigFile {
|
|
@@ -26,6 +27,12 @@ interface ConfigFile {
|
|
|
26
27
|
activeEnvironment: Environment;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
interface JwtPayload {
|
|
31
|
+
exp: number;
|
|
32
|
+
iat?: number;
|
|
33
|
+
sub?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
export class ClientConfiguration {
|
|
30
37
|
private configPath = `${os.homedir()}/.cred`;
|
|
31
38
|
private config: ConfigFile;
|
|
@@ -50,18 +57,23 @@ export class ClientConfiguration {
|
|
|
50
57
|
}
|
|
51
58
|
} catch (error) {
|
|
52
59
|
logger.warn(error);
|
|
60
|
+
const configPerEnvironment: ConfigFile["configPerEnvironment"] = {};
|
|
61
|
+
for (const env of ALL_ENVIRONMENTS) {
|
|
62
|
+
configPerEnvironment[env] = {};
|
|
63
|
+
}
|
|
53
64
|
this.config = {
|
|
54
|
-
configPerEnvironment
|
|
55
|
-
production: {},
|
|
56
|
-
staging: {},
|
|
57
|
-
localhost: {},
|
|
58
|
-
},
|
|
65
|
+
configPerEnvironment,
|
|
59
66
|
activeEnvironment: "production",
|
|
60
67
|
};
|
|
61
|
-
// Ensure the config file exists with default settings
|
|
62
68
|
fs.writeFileSync(this.configPath, JSON.stringify(this.config));
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
for (const env of ALL_ENVIRONMENTS) {
|
|
72
|
+
if (!this.config.configPerEnvironment[env]) {
|
|
73
|
+
this.config.configPerEnvironment[env] = {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
return this.config;
|
|
66
78
|
}
|
|
67
79
|
|
|
@@ -97,7 +109,8 @@ export class ClientConfiguration {
|
|
|
97
109
|
public setAccessToken(userToken: boolean, accessToken: string) {
|
|
98
110
|
this.loadConfig();
|
|
99
111
|
const environment = this.getEnvironment();
|
|
100
|
-
this.config.configPerEnvironment[environment].isServiceAccountToken =
|
|
112
|
+
this.config.configPerEnvironment[environment].isServiceAccountToken =
|
|
113
|
+
!userToken;
|
|
101
114
|
this.config.configPerEnvironment[environment].accessToken = accessToken;
|
|
102
115
|
fs.writeFileSync(this.configPath, JSON.stringify(this.config));
|
|
103
116
|
}
|
|
@@ -105,7 +118,8 @@ export class ClientConfiguration {
|
|
|
105
118
|
public getAuthenticationHeader() {
|
|
106
119
|
const environment = this.getEnvironment();
|
|
107
120
|
const jwt = this.config.configPerEnvironment[environment].accessToken;
|
|
108
|
-
const isServiceAccountToken =
|
|
121
|
+
const isServiceAccountToken =
|
|
122
|
+
this.config.configPerEnvironment[environment].isServiceAccountToken;
|
|
109
123
|
const authType = isServiceAccountToken ? "ApiKey" : "Bearer";
|
|
110
124
|
return `${authType} ${jwt}`;
|
|
111
125
|
}
|
|
@@ -192,13 +206,58 @@ export class ClientConfiguration {
|
|
|
192
206
|
return this.config.activeEnvironment || "production";
|
|
193
207
|
}
|
|
194
208
|
|
|
209
|
+
public isTokenValid(): { valid: boolean; reason?: string } {
|
|
210
|
+
this.loadConfig();
|
|
211
|
+
const environment = this.getEnvironment();
|
|
212
|
+
const accessToken =
|
|
213
|
+
this.config.configPerEnvironment[environment]?.accessToken;
|
|
214
|
+
|
|
215
|
+
if (!accessToken) {
|
|
216
|
+
return { valid: false, reason: "No token found. Please login." };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const isServiceAccountToken =
|
|
220
|
+
this.config.configPerEnvironment[environment]?.isServiceAccountToken;
|
|
221
|
+
if (isServiceAccountToken) {
|
|
222
|
+
return { valid: true };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const decoded = jwtDecode<JwtPayload>(accessToken);
|
|
227
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
228
|
+
|
|
229
|
+
if (decoded.exp && decoded.exp < currentTime) {
|
|
230
|
+
return {
|
|
231
|
+
valid: false,
|
|
232
|
+
reason: "Token expired. Please login again.",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { valid: true };
|
|
237
|
+
} catch {
|
|
238
|
+
return {
|
|
239
|
+
valid: false,
|
|
240
|
+
reason: "Invalid token format. Please login again.",
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
195
245
|
public printStatus() {
|
|
196
246
|
this.loadConfig();
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
247
|
+
const tokenStatus = this.isTokenValid();
|
|
248
|
+
|
|
249
|
+
if (tokenStatus.valid) {
|
|
250
|
+
const organization = this.getOrganization();
|
|
251
|
+
const project = this.getProject();
|
|
252
|
+
|
|
253
|
+
console.log(kleur.green(ASCII_TITLE));
|
|
254
|
+
logger.info(`Organization: ${kleur.green(organization || "Not set")}`);
|
|
255
|
+
logger.info(`Project: ${kleur.green(project || "Not set")}`);
|
|
256
|
+
} else {
|
|
257
|
+
logger.error("Failed to get status");
|
|
258
|
+
logger.error(
|
|
259
|
+
tokenStatus.reason || "Please, make sure that you are logged in",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
203
262
|
}
|
|
204
263
|
}
|
package/src/commands/add.ts
CHANGED
|
@@ -21,11 +21,17 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
21
21
|
"--readmeFile <readmeFile>",
|
|
22
22
|
"The path to the readme file for the project.",
|
|
23
23
|
)
|
|
24
|
+
.option(
|
|
25
|
+
"--replication <replicationCount>",
|
|
26
|
+
"The replication count (must be at least 1).",
|
|
27
|
+
parseInt,
|
|
28
|
+
)
|
|
24
29
|
.action((projectName, options) => {
|
|
25
30
|
createProject(
|
|
26
31
|
controlPlanesByEnv[environment],
|
|
27
32
|
projectName,
|
|
28
33
|
options.readmeFile,
|
|
34
|
+
options.replication,
|
|
29
35
|
options.debug,
|
|
30
36
|
options.yes,
|
|
31
37
|
);
|
|
@@ -44,6 +50,11 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
44
50
|
"--exclude-tables <excludeTables>",
|
|
45
51
|
"The list of tables to exclude. Ex: {dataset/schema}.{table}",
|
|
46
52
|
)
|
|
53
|
+
.option(
|
|
54
|
+
"--skip-indexing",
|
|
55
|
+
"Disable automatic indexing for this connection.",
|
|
56
|
+
false,
|
|
57
|
+
)
|
|
47
58
|
.option("--debug")
|
|
48
59
|
.option("-y, --yes", "Skip confirmation.")
|
|
49
60
|
.action((connectionFileName, options) => {
|
|
@@ -52,6 +63,7 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
52
63
|
connectionFileName,
|
|
53
64
|
options.includeTables,
|
|
54
65
|
options.excludeTables,
|
|
66
|
+
options.skipIndexing,
|
|
55
67
|
options.debug,
|
|
56
68
|
options.yes,
|
|
57
69
|
);
|
|
@@ -90,27 +102,24 @@ export const registerAddCommands = (cli: Command, environment: Environment) => {
|
|
|
90
102
|
);
|
|
91
103
|
});
|
|
92
104
|
|
|
93
|
-
|
|
94
105
|
add.command("group-access-token")
|
|
95
106
|
.description("Create an access token for a group.")
|
|
96
|
-
.argument(
|
|
97
|
-
"<groupName>",
|
|
98
|
-
"The name of the group",
|
|
99
|
-
)
|
|
107
|
+
.argument("<groupName>", "The name of the group")
|
|
100
108
|
.argument("<tokenName>", "The name of the token to create")
|
|
101
109
|
.option(
|
|
102
110
|
"-e, --expires-at <expiresAt>",
|
|
103
111
|
"Token expiration date in ISO 8601 format (e.g., 2025-12-31T23:59:59Z). If not provided, token will expire in 99 years.",
|
|
104
112
|
)
|
|
105
113
|
.option("--debug")
|
|
114
|
+
.option("-j, --json-output", "Output only JSON (for use in scripts)")
|
|
106
115
|
.action((groupName, tokenName, options) => {
|
|
107
116
|
createGroupAccessToken(
|
|
108
117
|
controlPlanesByEnv[environment],
|
|
109
118
|
groupName,
|
|
110
119
|
tokenName,
|
|
111
120
|
options.expiresAt,
|
|
121
|
+
options.jsonOutput === true,
|
|
112
122
|
options.debug || false,
|
|
113
123
|
);
|
|
114
124
|
});
|
|
115
|
-
|
|
116
125
|
};
|