@quonfig/cli 0.0.5 → 0.0.7
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 +209 -161
- package/dist/codegen/local-config-reader.d.ts +6 -0
- package/dist/codegen/local-config-reader.js +113 -0
- package/dist/commands/create.js +3 -0
- package/dist/commands/generate.d.ts +3 -2
- package/dist/commands/generate.js +18 -10
- package/dist/commands/login.js +22 -15
- package/dist/commands/profile.d.ts +1 -0
- package/dist/commands/profile.js +6 -42
- package/dist/commands/sdk-key/create.d.ts +11 -0
- package/dist/commands/sdk-key/create.js +100 -0
- package/dist/commands/sdk-key/list.d.ts +10 -0
- package/dist/commands/sdk-key/list.js +48 -0
- package/dist/commands/sdk-key/revoke.d.ts +10 -0
- package/dist/commands/sdk-key/revoke.js +30 -0
- package/dist/commands/sdk-key.d.ts +6 -0
- package/dist/commands/sdk-key.js +15 -0
- package/dist/commands/whoami.js +8 -8
- package/dist/commands/workspace/switch.d.ts +7 -0
- package/dist/commands/workspace/switch.js +90 -0
- package/dist/commands/workspace.js +14 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -2
- package/dist/interactive-prompt.js +0 -4
- package/dist/ui/get-value.js +4 -1
- package/dist/util/get-client.js +34 -11
- package/dist/util/token-storage.d.ts +6 -0
- package/dist/util/token-storage.js +17 -0
- package/dist/verify/validate.js +28 -8
- package/oclif.manifest.json +433 -118
- package/package.json +1 -1
- package/dist/codegen/config-downloader.d.ts +0 -7
- package/dist/codegen/config-downloader.js +0 -26
- package/dist/commands/download.d.ts +0 -13
- package/dist/commands/download.js +0 -63
- package/dist/commands/serve.d.ts +0 -13
- package/dist/commands/serve.js +0 -101
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54",
|
|
3
3
|
"name": "@quonfig/cli",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.7",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/quonfig/cli/issues"
|
|
7
7
|
},
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export class ConfigDownloader {
|
|
2
|
-
command;
|
|
3
|
-
constructor(command) {
|
|
4
|
-
this.command = command;
|
|
5
|
-
}
|
|
6
|
-
async downloadConfig() {
|
|
7
|
-
try {
|
|
8
|
-
const response = (await this.command.apiClient.get('/all-config-types/v1/download-all-envs'));
|
|
9
|
-
if (!response.ok) {
|
|
10
|
-
throw new Error(`Failed to download config: ${response.status}`);
|
|
11
|
-
}
|
|
12
|
-
// Parse the response data - configs are nested in response.data.json
|
|
13
|
-
const configData = response.json;
|
|
14
|
-
// Print out each config key
|
|
15
|
-
this.command.verboseLog('\nFound configurations:', configData.configs?.length || 0);
|
|
16
|
-
if (!configData.configs) {
|
|
17
|
-
throw new Error('Invalid response format');
|
|
18
|
-
}
|
|
19
|
-
return configData;
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
console.error('Error downloading config:', error);
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { JsonObj } from '../result.js';
|
|
2
|
-
import { APICommand } from '../index.js';
|
|
3
|
-
export default class Download extends APICommand {
|
|
4
|
-
static description: string;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
static flags: {
|
|
7
|
-
environment: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
-
'sdk-key': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
-
};
|
|
10
|
-
init(): Promise<void>;
|
|
11
|
-
run(): Promise<JsonObj | void>;
|
|
12
|
-
private writeFile;
|
|
13
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { APICommand } from '../index.js';
|
|
5
|
-
import { getProjectEnvFromSdkKey } from '@quonfig/node';
|
|
6
|
-
import getEnvironment from '../ui/get-environment.js';
|
|
7
|
-
import { checkmark } from '../util/color.js';
|
|
8
|
-
import getClient from '../util/get-client.js';
|
|
9
|
-
export default class Download extends APICommand {
|
|
10
|
-
static description = `Download a Datafile for a given environment
|
|
11
|
-
|
|
12
|
-
You can serve a datafile using the \`serve\` command.`;
|
|
13
|
-
static examples = [
|
|
14
|
-
'<%= config.bin %> <%= command.id %> --environment=test',
|
|
15
|
-
'<%= config.bin %> <%= command.id %> --environment=test --sdk-key=YOUR_SDK_KEY',
|
|
16
|
-
];
|
|
17
|
-
static flags = {
|
|
18
|
-
environment: Flags.string({ description: 'environment to download' }),
|
|
19
|
-
'sdk-key': Flags.string({
|
|
20
|
-
description: 'SDK key for authentication (uses legacy download endpoint)',
|
|
21
|
-
required: false,
|
|
22
|
-
}),
|
|
23
|
-
};
|
|
24
|
-
async init() {
|
|
25
|
-
await super.init();
|
|
26
|
-
const { flags } = await this.parse(Download);
|
|
27
|
-
// If SDK key is provided, reinitialize the client with it
|
|
28
|
-
if (flags['sdk-key']) {
|
|
29
|
-
this.rawApiClient = await getClient(this, flags['sdk-key'], flags.profile);
|
|
30
|
-
this.currentEnvironment = getProjectEnvFromSdkKey(flags['sdk-key']);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async run() {
|
|
34
|
-
const { flags } = await this.parse(Download);
|
|
35
|
-
const environment = await getEnvironment({
|
|
36
|
-
command: this,
|
|
37
|
-
flags,
|
|
38
|
-
message: 'Which environment would you like to download?',
|
|
39
|
-
providedEnvironment: flags.environment,
|
|
40
|
-
});
|
|
41
|
-
if (!environment) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
this.verboseLog({ environment, usingSdkKey: Boolean(flags['sdk-key']) });
|
|
45
|
-
// Use legacy endpoint if SDK key is provided, otherwise use new OAuth endpoint
|
|
46
|
-
const downloadUrl = flags['sdk-key']
|
|
47
|
-
? `/api/v2/configs/download?envId=${environment.id}`
|
|
48
|
-
: `/all-config-types/v1/download?envId=${environment.id}`;
|
|
49
|
-
const download = await this.apiClient.get(downloadUrl);
|
|
50
|
-
if (download.ok) {
|
|
51
|
-
return this.writeFile(download, environment);
|
|
52
|
-
}
|
|
53
|
-
this.verboseLog({ result: download });
|
|
54
|
-
return this.err(`Failed to download file. Status=${download.status}`, download.error);
|
|
55
|
-
}
|
|
56
|
-
writeFile(result, environment) {
|
|
57
|
-
const fileName = `quonfig.${environment.name}.${environment.id}.config.json`;
|
|
58
|
-
const filePath = path.join(process.cwd(), fileName);
|
|
59
|
-
fs.writeFileSync(filePath, JSON.stringify(result.json, null, 2));
|
|
60
|
-
this.log(`${checkmark} Successfully downloaded ${fileName}`);
|
|
61
|
-
return { filePath, succes: true };
|
|
62
|
-
}
|
|
63
|
-
}
|
package/dist/commands/serve.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { BaseCommand } from '../index.js';
|
|
2
|
-
export default class Serve extends BaseCommand {
|
|
3
|
-
static args: {
|
|
4
|
-
'data-file': import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
5
|
-
};
|
|
6
|
-
static description: string;
|
|
7
|
-
static examples: string[];
|
|
8
|
-
static flags: {
|
|
9
|
-
port: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
-
};
|
|
11
|
-
run(): Promise<void>;
|
|
12
|
-
private requestHandler;
|
|
13
|
-
}
|
package/dist/commands/serve.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import { ConfigType } from '@quonfig/node';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import http from 'node:http';
|
|
5
|
-
import { BaseCommand } from '../index.js';
|
|
6
|
-
import { initQuonfig } from '../quonfig.js';
|
|
7
|
-
import { valueTypeStringForConfig } from '@quonfig/node';
|
|
8
|
-
import { javaScriptClientFormattedContextToContext } from '../util/context.js';
|
|
9
|
-
const allowCORSPreflight = (res) => {
|
|
10
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
11
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
12
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-quonfig-client-version, authorization');
|
|
13
|
-
};
|
|
14
|
-
const ALWAYS_SEND_CONFIG_TYPES = new Set([ConfigType.FeatureFlag, ConfigType.LogLevel]);
|
|
15
|
-
const base64ToBytes = (base64) => {
|
|
16
|
-
const binString = Buffer.from(base64, 'base64').toString('binary');
|
|
17
|
-
return Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
18
|
-
};
|
|
19
|
-
export default class Serve extends BaseCommand {
|
|
20
|
-
static args = {
|
|
21
|
-
'data-file': Args.string({ description: 'file to read', required: true }),
|
|
22
|
-
};
|
|
23
|
-
static description = `Serve a datafile on a local port
|
|
24
|
-
|
|
25
|
-
You can download a datafile using the \`download\` command.
|
|
26
|
-
|
|
27
|
-
You'll need to update your JavaScript (or React) client to point to this server.
|
|
28
|
-
|
|
29
|
-
e.g. \`endpoints: ["http://localhost:3099"],\`
|
|
30
|
-
`;
|
|
31
|
-
static examples = ['<%= config.bin %> <%= command.id %> ./quonfig.test.588.config.json --port=3099 '];
|
|
32
|
-
static flags = {
|
|
33
|
-
port: Flags.integer({ default: 3099, description: 'port to serve on' }),
|
|
34
|
-
};
|
|
35
|
-
async run() {
|
|
36
|
-
const { args, flags } = await this.parse(Serve);
|
|
37
|
-
const file = args['data-file'];
|
|
38
|
-
if (!fs.existsSync(file)) {
|
|
39
|
-
return this.error(`File not found: ${file}`);
|
|
40
|
-
}
|
|
41
|
-
let quonfig;
|
|
42
|
-
try {
|
|
43
|
-
quonfig = await initQuonfig(this, file);
|
|
44
|
-
if (!quonfig) {
|
|
45
|
-
throw new Error('Quonfig not initialized');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (error_) {
|
|
49
|
-
const error = error_;
|
|
50
|
-
if (/No projectEnvId found in config/.test(error.message) ||
|
|
51
|
-
/is not iterable/.test(error.message) ||
|
|
52
|
-
/Unexpected end of JSON input/.test(error.message)) {
|
|
53
|
-
return this.error(`${error.message}\nYour download file seems invalid or corrupt. Please redownload your datafile.`);
|
|
54
|
-
}
|
|
55
|
-
return this.error(error);
|
|
56
|
-
}
|
|
57
|
-
const { port } = flags;
|
|
58
|
-
const server = http.createServer(this.requestHandler(quonfig));
|
|
59
|
-
server.listen(port, (err) => {
|
|
60
|
-
if (err) {
|
|
61
|
-
return this.err(err);
|
|
62
|
-
}
|
|
63
|
-
console.log(`Server is listening on ${port}. Press ctrl-c to stop.`);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
requestHandler(quonfig) {
|
|
67
|
-
return (req, res) => {
|
|
68
|
-
allowCORSPreflight(res);
|
|
69
|
-
// Handle CORS preflight request
|
|
70
|
-
if (req.method === 'OPTIONS') {
|
|
71
|
-
res.writeHead(204);
|
|
72
|
-
res.end();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const match = req.url?.match(/^\/?\/configs\/eval-with-context\/(.+)$/);
|
|
76
|
-
if (req.method === 'GET' && match) {
|
|
77
|
-
const encodedContext = match[1].split('?')[0];
|
|
78
|
-
console.log(`Received context: ${encodedContext}`);
|
|
79
|
-
console.log(`Decoded context: ${decodeURIComponent(encodedContext)}`);
|
|
80
|
-
const decoded = new TextDecoder().decode(base64ToBytes(decodeURIComponent(encodedContext)));
|
|
81
|
-
const context = javaScriptClientFormattedContextToContext(JSON.parse(decoded));
|
|
82
|
-
this.log(`${new Date().toISOString()}: Provided context: ${JSON.stringify(context)}`);
|
|
83
|
-
const config = {};
|
|
84
|
-
for (const key of quonfig.keys()) {
|
|
85
|
-
const raw = quonfig.rawConfig(key);
|
|
86
|
-
if (raw && (ALWAYS_SEND_CONFIG_TYPES.has(raw.type) || raw.sendToClientSdk)) {
|
|
87
|
-
const valueType = valueTypeStringForConfig(raw) ?? '?';
|
|
88
|
-
config[key] = { value: { [valueType]: quonfig.get(key, context) } };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
92
|
-
res.end(JSON.stringify({ evaluations: config }));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
this.verboseLog(`No handler for ${req.method} ${req.url}`);
|
|
96
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
97
|
-
res.end('Not Found. You can only GET /configs/eval-with-context/<context>');
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|