@quonfig/cli 0.0.1
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/LICENSE +21 -0
- package/README.md +707 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +9 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +8 -0
- package/dist/api/get-environments.d.ts +10 -0
- package/dist/api/get-environments.js +11 -0
- package/dist/codegen/code-generators/base-generator.d.ts +11 -0
- package/dist/codegen/code-generators/base-generator.js +8 -0
- package/dist/codegen/code-generators/base-typescript-generator.d.ts +17 -0
- package/dist/codegen/code-generators/base-typescript-generator.js +35 -0
- package/dist/codegen/code-generators/node-typescript-generator.d.ts +8 -0
- package/dist/codegen/code-generators/node-typescript-generator.js +96 -0
- package/dist/codegen/code-generators/react-typescript-generator.d.ts +11 -0
- package/dist/codegen/code-generators/react-typescript-generator.js +116 -0
- package/dist/codegen/config-downloader.d.ts +7 -0
- package/dist/codegen/config-downloader.js +26 -0
- package/dist/codegen/json-schema-to-zod.d.ts +3 -0
- package/dist/codegen/json-schema-to-zod.js +170 -0
- package/dist/codegen/language-mappers/json-to-zod-mapper.d.ts +4 -0
- package/dist/codegen/language-mappers/json-to-zod-mapper.js +55 -0
- package/dist/codegen/language-mappers/zod-base-mapper.d.ts +35 -0
- package/dist/codegen/language-mappers/zod-base-mapper.js +111 -0
- package/dist/codegen/language-mappers/zod-to-string-mapper.d.ts +25 -0
- package/dist/codegen/language-mappers/zod-to-string-mapper.js +87 -0
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.d.ts +35 -0
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.js +133 -0
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.d.ts +36 -0
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.js +150 -0
- package/dist/codegen/mustache-extractor.d.ts +6 -0
- package/dist/codegen/mustache-extractor.js +83 -0
- package/dist/codegen/schema-evaluator.d.ts +37 -0
- package/dist/codegen/schema-evaluator.js +283 -0
- package/dist/codegen/schema-extractor.d.ts +24 -0
- package/dist/codegen/schema-extractor.js +265 -0
- package/dist/codegen/types.d.ts +44 -0
- package/dist/codegen/types.js +5 -0
- package/dist/codegen/zod-introspection.d.ts +85 -0
- package/dist/codegen/zod-introspection.js +205 -0
- package/dist/commands/create.d.ts +19 -0
- package/dist/commands/create.js +175 -0
- package/dist/commands/download.d.ts +13 -0
- package/dist/commands/download.js +63 -0
- package/dist/commands/generate-new-hex-key.d.ts +7 -0
- package/dist/commands/generate-new-hex-key.js +10 -0
- package/dist/commands/generate.d.ts +16 -0
- package/dist/commands/generate.js +220 -0
- package/dist/commands/get.d.ts +13 -0
- package/dist/commands/get.js +141 -0
- package/dist/commands/info.d.ts +21 -0
- package/dist/commands/info.js +464 -0
- package/dist/commands/interactive.d.ts +5 -0
- package/dist/commands/interactive.js +8 -0
- package/dist/commands/list.d.ts +13 -0
- package/dist/commands/list.js +61 -0
- package/dist/commands/login.d.ts +10 -0
- package/dist/commands/login.js +124 -0
- package/dist/commands/logout.d.ts +7 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +145 -0
- package/dist/commands/override.d.ts +17 -0
- package/dist/commands/override.js +170 -0
- package/dist/commands/profile.d.ts +7 -0
- package/dist/commands/profile.js +47 -0
- package/dist/commands/schema.d.ts +21 -0
- package/dist/commands/schema.js +168 -0
- package/dist/commands/serve.d.ts +13 -0
- package/dist/commands/serve.js +99 -0
- package/dist/commands/set-default.d.ts +20 -0
- package/dist/commands/set-default.js +273 -0
- package/dist/commands/verify.d.ts +12 -0
- package/dist/commands/verify.js +48 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +52 -0
- package/dist/commands/workspace.d.ts +7 -0
- package/dist/commands/workspace.js +38 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +10 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +112 -0
- package/dist/interactive-prompt.d.ts +2 -0
- package/dist/interactive-prompt.js +162 -0
- package/dist/quonfig.d.ts +15 -0
- package/dist/quonfig.js +75 -0
- package/dist/result.d.ts +35 -0
- package/dist/result.js +9 -0
- package/dist/services/mcp.d.ts +15 -0
- package/dist/services/mcp.js +59 -0
- package/dist/ui/get-confirmation.d.ts +10 -0
- package/dist/ui/get-confirmation.js +4 -0
- package/dist/ui/get-environment.d.ts +11 -0
- package/dist/ui/get-environment.js +42 -0
- package/dist/ui/get-key.d.ts +27 -0
- package/dist/ui/get-key.js +15 -0
- package/dist/ui/get-string.d.ts +5 -0
- package/dist/ui/get-string.js +8 -0
- package/dist/ui/get-value.d.ts +18 -0
- package/dist/ui/get-value.js +46 -0
- package/dist/util/autocomplete.d.ts +6 -0
- package/dist/util/autocomplete.js +25 -0
- package/dist/util/coerce.d.ts +7 -0
- package/dist/util/coerce.js +62 -0
- package/dist/util/color.d.ts +3 -0
- package/dist/util/color.js +4 -0
- package/dist/util/config-value-dto.d.ts +16 -0
- package/dist/util/config-value-dto.js +82 -0
- package/dist/util/context.d.ts +18 -0
- package/dist/util/context.js +10 -0
- package/dist/util/domain-urls.d.ts +3 -0
- package/dist/util/domain-urls.js +18 -0
- package/dist/util/encryption.d.ts +20 -0
- package/dist/util/encryption.js +153 -0
- package/dist/util/file-manager.d.ts +12 -0
- package/dist/util/file-manager.js +16 -0
- package/dist/util/get-client.d.ts +7 -0
- package/dist/util/get-client.js +127 -0
- package/dist/util/is-interactive.d.ts +5 -0
- package/dist/util/is-interactive.js +7 -0
- package/dist/util/json-maybe.d.ts +3 -0
- package/dist/util/json-maybe.js +14 -0
- package/dist/util/name-arg.d.ts +4 -0
- package/dist/util/name-arg.js +3 -0
- package/dist/util/oauth-client.d.ts +30 -0
- package/dist/util/oauth-client.js +99 -0
- package/dist/util/secret-flags.d.ts +13 -0
- package/dist/util/secret-flags.js +13 -0
- package/dist/util/token-storage.d.ts +25 -0
- package/dist/util/token-storage.js +90 -0
- package/dist/validations/value.d.ts +4 -0
- package/dist/validations/value.js +11 -0
- package/dist/verify/standalone.d.ts +14 -0
- package/dist/verify/standalone.js +133 -0
- package/dist/verify/validate.d.ts +43 -0
- package/dist/verify/validate.js +756 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/oclif.manifest.json +1429 -0
- package/package.json +104 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Environment } from '../api/get-environments.js';
|
|
2
|
+
import { APICommand } from '../index.js';
|
|
3
|
+
import { JsonObj } from '../result.js';
|
|
4
|
+
declare const getEnvironment: ({ allowDefaultEnvironment, command, flags, message, providedEnvironment, }: {
|
|
5
|
+
allowDefaultEnvironment?: boolean;
|
|
6
|
+
command: APICommand;
|
|
7
|
+
flags: JsonObj;
|
|
8
|
+
message: string;
|
|
9
|
+
providedEnvironment: string | undefined;
|
|
10
|
+
}) => Promise<Environment | undefined>;
|
|
11
|
+
export default getEnvironment;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getEnvironments } from '../api/get-environments.js';
|
|
2
|
+
import autocomplete from '../util/autocomplete.js';
|
|
3
|
+
import isInteractive from '../util/is-interactive.js';
|
|
4
|
+
const defaultEnvironmentName = '[Default]';
|
|
5
|
+
const getEnvironment = async ({ allowDefaultEnvironment = false, command, flags, message, providedEnvironment, }) => {
|
|
6
|
+
if (!providedEnvironment && !isInteractive(flags)) {
|
|
7
|
+
command.err("'environment' is required when interactive mode isn't available.");
|
|
8
|
+
}
|
|
9
|
+
const environments = await getEnvironments(command);
|
|
10
|
+
command.verboseLog({ environments });
|
|
11
|
+
if (providedEnvironment?.toLowerCase() === defaultEnvironmentName.toLowerCase()) {
|
|
12
|
+
return {
|
|
13
|
+
id: '',
|
|
14
|
+
name: defaultEnvironmentName,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (providedEnvironment) {
|
|
18
|
+
const matchingEnvironment = environments.find((environment) => environment.name.toLowerCase() === providedEnvironment.toLowerCase());
|
|
19
|
+
if (!matchingEnvironment) {
|
|
20
|
+
command.err(`Environment \`${providedEnvironment}\` not found. Valid environments: ${environments
|
|
21
|
+
.map((environment) => environment.name)
|
|
22
|
+
.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
return matchingEnvironment;
|
|
25
|
+
}
|
|
26
|
+
const environmentNames = environments.map((environment) => environment.name);
|
|
27
|
+
if (allowDefaultEnvironment) {
|
|
28
|
+
environmentNames.unshift(defaultEnvironmentName);
|
|
29
|
+
}
|
|
30
|
+
const selectedEnvironment = await autocomplete({
|
|
31
|
+
message,
|
|
32
|
+
source: environmentNames,
|
|
33
|
+
});
|
|
34
|
+
if (selectedEnvironment === defaultEnvironmentName) {
|
|
35
|
+
return {
|
|
36
|
+
id: '',
|
|
37
|
+
name: defaultEnvironmentName,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return environments.find((environment) => environment.name === selectedEnvironment);
|
|
41
|
+
};
|
|
42
|
+
export default getEnvironment;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Quonfig } from '@quonfig/node';
|
|
2
|
+
type Flags = {
|
|
3
|
+
['sdk-key']?: string;
|
|
4
|
+
interactive?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type CommandLike = {
|
|
7
|
+
err: (input: Error | string, options?: {
|
|
8
|
+
code?: string | undefined;
|
|
9
|
+
exit: false;
|
|
10
|
+
}) => void;
|
|
11
|
+
error: (input: Error | string, options: {
|
|
12
|
+
code?: string | undefined;
|
|
13
|
+
exit: false;
|
|
14
|
+
}) => void;
|
|
15
|
+
};
|
|
16
|
+
declare const getKey: ({ args, command, flags, message, }: {
|
|
17
|
+
args: {
|
|
18
|
+
name?: string;
|
|
19
|
+
};
|
|
20
|
+
command: CommandLike;
|
|
21
|
+
flags: Flags;
|
|
22
|
+
message: string;
|
|
23
|
+
}) => Promise<{
|
|
24
|
+
key: null | string | undefined;
|
|
25
|
+
quonfig: Quonfig | undefined;
|
|
26
|
+
}>;
|
|
27
|
+
export default getKey;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { initQuonfig } from '../quonfig.js';
|
|
2
|
+
import autocomplete from '../util/autocomplete.js';
|
|
3
|
+
import isInteractive from '../util/is-interactive.js';
|
|
4
|
+
const getKey = async ({ args, command, flags, message, }) => {
|
|
5
|
+
if (!args.name && !isInteractive(flags)) {
|
|
6
|
+
command.err("'name' argument is required when interactive mode isn't available.");
|
|
7
|
+
}
|
|
8
|
+
const quonfig = await initQuonfig(command, flags);
|
|
9
|
+
let key = args.name;
|
|
10
|
+
if (!key) {
|
|
11
|
+
key = await autocomplete({ message, source: () => quonfig.keys() });
|
|
12
|
+
}
|
|
13
|
+
return { key, quonfig };
|
|
14
|
+
};
|
|
15
|
+
export default getKey;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Quonfig } from '@quonfig/node';
|
|
2
|
+
import { Result } from '../result.js';
|
|
3
|
+
interface Environment {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
declare const getValue: ({ allowBlank, desiredValue, environment, flags, key, message, quonfig, }: {
|
|
8
|
+
allowBlank?: boolean;
|
|
9
|
+
desiredValue: string | undefined;
|
|
10
|
+
environment?: Environment;
|
|
11
|
+
flags: {
|
|
12
|
+
interactive: boolean;
|
|
13
|
+
};
|
|
14
|
+
key?: string;
|
|
15
|
+
message: string;
|
|
16
|
+
quonfig: Quonfig;
|
|
17
|
+
}) => Promise<Result<string>>;
|
|
18
|
+
export default getValue;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defaultValueFor } from '../quonfig.js';
|
|
2
|
+
import { failure, noop, success } from '../result.js';
|
|
3
|
+
import validateValue from '../validations/value.js';
|
|
4
|
+
import getString from './get-string.js';
|
|
5
|
+
const getValue = async ({ allowBlank = true, desiredValue, environment, flags, key, message, quonfig, }) => {
|
|
6
|
+
if (desiredValue === undefined && !flags.interactive) {
|
|
7
|
+
return failure(`No value provided for ${key}`);
|
|
8
|
+
}
|
|
9
|
+
if (!key) {
|
|
10
|
+
const value = desiredValue ?? (await promptForValue({ allowBlank, message }));
|
|
11
|
+
if (value === undefined || value === null) {
|
|
12
|
+
return noop();
|
|
13
|
+
}
|
|
14
|
+
return success(value);
|
|
15
|
+
}
|
|
16
|
+
const currentDefault = environment ? defaultValueFor(environment.id, key) : undefined;
|
|
17
|
+
const config = quonfig.rawConfig(key);
|
|
18
|
+
if (!config) {
|
|
19
|
+
return failure(`Could not find config named ${key}`);
|
|
20
|
+
}
|
|
21
|
+
const selectedValue = desiredValue ?? (await promptForValue({ allowBlank, config, currentDefault, message }));
|
|
22
|
+
if (selectedValue === undefined || selectedValue === null) {
|
|
23
|
+
return noop();
|
|
24
|
+
}
|
|
25
|
+
if (selectedValue === currentDefault?.value?.toString()) {
|
|
26
|
+
return noop(`The default is already \`${selectedValue}\``);
|
|
27
|
+
}
|
|
28
|
+
return validateValue(quonfig, key, selectedValue);
|
|
29
|
+
};
|
|
30
|
+
const valueToString = (v) => {
|
|
31
|
+
if (!v)
|
|
32
|
+
return '';
|
|
33
|
+
if (v.value === undefined || v.value === null)
|
|
34
|
+
return '';
|
|
35
|
+
return String(v.value);
|
|
36
|
+
};
|
|
37
|
+
const promptForValue = async ({ allowBlank, config, currentDefault, message, }) => {
|
|
38
|
+
// In the new quonfig format, allowable values aren't stored at the config level.
|
|
39
|
+
// Just prompt for a string input.
|
|
40
|
+
if (!currentDefault) {
|
|
41
|
+
return getString({ allowBlank, message });
|
|
42
|
+
}
|
|
43
|
+
const autoCompleteMessage = `The current default is \`${valueToString(currentDefault)}\`. Enter your new default.`;
|
|
44
|
+
return getString({ allowBlank, message: autoCompleteMessage });
|
|
45
|
+
};
|
|
46
|
+
export default getValue;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// eslint-disable-next-line import/default
|
|
2
|
+
import fuzzy from 'fuzzy';
|
|
3
|
+
import internalAutocomplete from 'inquirer-autocomplete-standalone';
|
|
4
|
+
import { green } from '../util/color.js';
|
|
5
|
+
const options = {
|
|
6
|
+
post: '\u001B[0m',
|
|
7
|
+
pre: '\u001B[31m',
|
|
8
|
+
};
|
|
9
|
+
const unformat = (str) => str.replaceAll(options.pre, '').replaceAll(options.post, '');
|
|
10
|
+
const autocomplete = async ({ message, source }) => {
|
|
11
|
+
try {
|
|
12
|
+
const result = await internalAutocomplete({
|
|
13
|
+
message: green(message),
|
|
14
|
+
async source(input) {
|
|
15
|
+
const list = typeof source === 'function' ? source() : source;
|
|
16
|
+
// eslint-disable-next-line import/no-named-as-default-member
|
|
17
|
+
return fuzzy.filter(input ?? '', list, options).map((el) => ({ value: el.string }));
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return unformat(result);
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
export default autocomplete;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConfigValue } from '@quonfig/node';
|
|
2
|
+
import { ConfigValueType } from '@quonfig/node';
|
|
3
|
+
type ConfigValueWithConfigValueType = [ConfigValue, ConfigValueType];
|
|
4
|
+
export declare const TYPE_MAPPING: Record<string, ConfigValueType>;
|
|
5
|
+
export declare const coerceIntoType: (type: string, value: string) => ConfigValueWithConfigValueType | undefined;
|
|
6
|
+
export declare const coerceBool: (value: string) => boolean;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ConfigValueType } from '@quonfig/node';
|
|
2
|
+
const TRUE_VALUES = new Set(['true', '1', 't']);
|
|
3
|
+
const BOOLEAN_VALUES = new Set([...TRUE_VALUES, 'false', '0', 'f']);
|
|
4
|
+
export const TYPE_MAPPING = {
|
|
5
|
+
bool: ConfigValueType.Bool,
|
|
6
|
+
boolean: ConfigValueType.Bool,
|
|
7
|
+
double: ConfigValueType.Double,
|
|
8
|
+
int: ConfigValueType.Int,
|
|
9
|
+
string: ConfigValueType.String,
|
|
10
|
+
'string-list': ConfigValueType.StringList,
|
|
11
|
+
stringList: ConfigValueType.StringList,
|
|
12
|
+
};
|
|
13
|
+
export const coerceIntoType = (type, value) => {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case 'string': {
|
|
16
|
+
return [{ string: value }, TYPE_MAPPING[type]];
|
|
17
|
+
}
|
|
18
|
+
case 'int': {
|
|
19
|
+
try {
|
|
20
|
+
const int = BigInt(value);
|
|
21
|
+
return [{ int }, TYPE_MAPPING[type]];
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new TypeError(`Invalid default value for int: ${value}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
case 'double': {
|
|
28
|
+
const double = Number.parseFloat(value);
|
|
29
|
+
if (Number.isNaN(double)) {
|
|
30
|
+
throw new TypeError(`Invalid default value for double: ${value}`);
|
|
31
|
+
}
|
|
32
|
+
return [{ double }, TYPE_MAPPING[type]];
|
|
33
|
+
}
|
|
34
|
+
case 'bool':
|
|
35
|
+
case 'boolean': {
|
|
36
|
+
return [{ bool: coerceBool(value) }, TYPE_MAPPING[type]];
|
|
37
|
+
}
|
|
38
|
+
case 'stringList':
|
|
39
|
+
case 'string-list': {
|
|
40
|
+
return [{ stringList: { values: value.split(/\s*,\s*/) } }, TYPE_MAPPING[type]];
|
|
41
|
+
}
|
|
42
|
+
case 'json': {
|
|
43
|
+
try {
|
|
44
|
+
// ensure the value is valid JSON
|
|
45
|
+
JSON.parse(value);
|
|
46
|
+
return [{ json: { json: value } }, ConfigValueType.Json];
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new TypeError(`Invalid default value for JSON: ${value}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
default: {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const coerceBool = (value) => {
|
|
58
|
+
if (!BOOLEAN_VALUES.has(value.toLowerCase())) {
|
|
59
|
+
throw new TypeError(`Invalid default value for boolean: ${value}`);
|
|
60
|
+
}
|
|
61
|
+
return TRUE_VALUES.has(value.toLowerCase());
|
|
62
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ConfigValue } from '@quonfig/node';
|
|
2
|
+
import { ConfigValueType } from '@quonfig/node';
|
|
3
|
+
/**
|
|
4
|
+
* Maps a ConfigValue to the DTO format expected by the API.
|
|
5
|
+
* Used by both create and set-default commands.
|
|
6
|
+
* @param configValue - The config value to convert
|
|
7
|
+
* @param valueType - The type of the config value
|
|
8
|
+
* @returns The DTO representation of the config value
|
|
9
|
+
*/
|
|
10
|
+
export declare function mapConfigValueToDto(configValue: ConfigValue, valueType: ConfigValueType): Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Converts a ConfigValueType enum to its string representation for the API.
|
|
13
|
+
* @param valueType - The config value type enum
|
|
14
|
+
* @returns The string representation of the value type
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapValueTypeToString(valueType: ConfigValueType): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ConfigValueType } from '@quonfig/node';
|
|
2
|
+
/**
|
|
3
|
+
* Maps a ConfigValue to the DTO format expected by the API.
|
|
4
|
+
* Used by both create and set-default commands.
|
|
5
|
+
* @param configValue - The config value to convert
|
|
6
|
+
* @param valueType - The type of the config value
|
|
7
|
+
* @returns The DTO representation of the config value
|
|
8
|
+
*/
|
|
9
|
+
export function mapConfigValueToDto(configValue, valueType) {
|
|
10
|
+
const dto = {
|
|
11
|
+
type: mapValueTypeToString(valueType),
|
|
12
|
+
};
|
|
13
|
+
// Handle provided (env-var) values
|
|
14
|
+
if (configValue.provided) {
|
|
15
|
+
return {
|
|
16
|
+
...dto,
|
|
17
|
+
provided: {
|
|
18
|
+
source: configValue.provided.source,
|
|
19
|
+
lookup: configValue.provided.lookup,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Extract the actual value based on type
|
|
24
|
+
let value;
|
|
25
|
+
if (configValue.bool !== undefined) {
|
|
26
|
+
value = configValue.bool;
|
|
27
|
+
}
|
|
28
|
+
else if (configValue.string !== undefined) {
|
|
29
|
+
value = configValue.string;
|
|
30
|
+
}
|
|
31
|
+
else if (configValue.int !== undefined) {
|
|
32
|
+
value = configValue.int;
|
|
33
|
+
}
|
|
34
|
+
else if (configValue.double !== undefined) {
|
|
35
|
+
value = configValue.double;
|
|
36
|
+
}
|
|
37
|
+
else if (configValue.stringList !== undefined) {
|
|
38
|
+
value = configValue.stringList.values;
|
|
39
|
+
}
|
|
40
|
+
else if (configValue.json !== undefined) {
|
|
41
|
+
value = configValue.json;
|
|
42
|
+
}
|
|
43
|
+
else if (configValue.duration !== undefined) {
|
|
44
|
+
value = configValue.duration;
|
|
45
|
+
}
|
|
46
|
+
else if (configValue.intRange !== undefined) {
|
|
47
|
+
value = configValue.intRange;
|
|
48
|
+
}
|
|
49
|
+
else if (configValue.value !== undefined) {
|
|
50
|
+
// Handle encrypted/confidential values where value is directly set
|
|
51
|
+
value = configValue.value;
|
|
52
|
+
}
|
|
53
|
+
dto.value = value;
|
|
54
|
+
if (configValue.confidential) {
|
|
55
|
+
dto.confidential = true;
|
|
56
|
+
}
|
|
57
|
+
if (configValue.decryptWith) {
|
|
58
|
+
dto.decryptWith = configValue.decryptWith;
|
|
59
|
+
}
|
|
60
|
+
return dto;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Converts a ConfigValueType enum to its string representation for the API.
|
|
64
|
+
* @param valueType - The config value type enum
|
|
65
|
+
* @returns The string representation of the value type
|
|
66
|
+
*/
|
|
67
|
+
export function mapValueTypeToString(valueType) {
|
|
68
|
+
const mapping = {
|
|
69
|
+
[ConfigValueType.Bool]: 'bool',
|
|
70
|
+
[ConfigValueType.String]: 'string',
|
|
71
|
+
[ConfigValueType.Int]: 'int',
|
|
72
|
+
[ConfigValueType.Double]: 'double',
|
|
73
|
+
[ConfigValueType.StringList]: 'string_list',
|
|
74
|
+
[ConfigValueType.Json]: 'json',
|
|
75
|
+
[ConfigValueType.LimitDefinition]: 'limit_definition',
|
|
76
|
+
[ConfigValueType.Duration]: 'duration',
|
|
77
|
+
[ConfigValueType.IntRange]: 'int_range',
|
|
78
|
+
[ConfigValueType.Bytes]: 'bytes',
|
|
79
|
+
[ConfigValueType.LogLevel]: 'log_level',
|
|
80
|
+
};
|
|
81
|
+
return mapping[valueType] || 'string';
|
|
82
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface JavascriptClientContext {
|
|
2
|
+
type: string;
|
|
3
|
+
values: {
|
|
4
|
+
[key: string]: {
|
|
5
|
+
string: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
interface JavascriptClientContextInput {
|
|
10
|
+
contexts: JavascriptClientContext[];
|
|
11
|
+
}
|
|
12
|
+
interface ContextObj {
|
|
13
|
+
[key: string]: {
|
|
14
|
+
[key: string]: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare const javaScriptClientFormattedContextToContext: (input: JavascriptClientContextInput) => ContextObj;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const javaScriptClientFormattedContextToContext = (input) => {
|
|
2
|
+
const result = {};
|
|
3
|
+
for (const context of input.contexts) {
|
|
4
|
+
result[context.type] = {};
|
|
5
|
+
for (const [key, value] of Object.entries(context.values)) {
|
|
6
|
+
result[context.type][key] = value.string;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const DEFAULT_DOMAIN = 'quonfig.com';
|
|
2
|
+
export const getDomain = () => process.env.QUONFIG_DOMAIN || DEFAULT_DOMAIN;
|
|
3
|
+
export const getApiUrl = (domain) => {
|
|
4
|
+
// Allow full override for local development
|
|
5
|
+
if (process.env.QUONFIG_API_BASE_URL_OVERRIDE) {
|
|
6
|
+
return process.env.QUONFIG_API_BASE_URL_OVERRIDE;
|
|
7
|
+
}
|
|
8
|
+
const actualDomain = domain || getDomain();
|
|
9
|
+
return `https://app.${actualDomain}`;
|
|
10
|
+
};
|
|
11
|
+
export const getAppUrl = (domain) => {
|
|
12
|
+
// Allow explicit override for app URL
|
|
13
|
+
if (process.env.QUONFIG_APP_BASE_URL_OVERRIDE) {
|
|
14
|
+
return process.env.QUONFIG_APP_BASE_URL_OVERRIDE;
|
|
15
|
+
}
|
|
16
|
+
const actualDomain = domain || getDomain();
|
|
17
|
+
return `https://app.${actualDomain}`;
|
|
18
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { APICommand } from '../index.js';
|
|
2
|
+
import type { Secret } from './secret-flags.js';
|
|
3
|
+
import { Result } from '../result.js';
|
|
4
|
+
/**
|
|
5
|
+
* Decrypts an encrypted config value
|
|
6
|
+
* Based on @quonfig/node/src/encryption.ts
|
|
7
|
+
* @param {string} encryptedString - The encrypted string in format: encryptedData--IV--authTag
|
|
8
|
+
* @param {string} keyStringHex - The encryption key as a 64-character hex string
|
|
9
|
+
* @returns {string} The decrypted plaintext value
|
|
10
|
+
*/
|
|
11
|
+
export declare function decrypt(encryptedString: string, keyStringHex: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Creates an encrypted ConfigValue using the new v1 API endpoints
|
|
14
|
+
* @param command - The APICommand instance for API calls and logging
|
|
15
|
+
* @param value - The plaintext value to encrypt
|
|
16
|
+
* @param secret - Secret configuration containing key name
|
|
17
|
+
* @param environmentId - Environment ID to fetch encryption key for (empty string for default)
|
|
18
|
+
* @returns Result<ConfigValue> with encrypted value or error
|
|
19
|
+
*/
|
|
20
|
+
export declare function makeConfidentialValue(command: APICommand, value: string, secret: Secret, environmentId: string): Promise<Result<any>>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { encryption } from '@quonfig/node';
|
|
2
|
+
import forge from 'node-forge';
|
|
3
|
+
import { failure, success } from '../result.js';
|
|
4
|
+
const SEPARATOR = '--';
|
|
5
|
+
const KEY_LENGTH = 32; // 32 bytes for AES-256
|
|
6
|
+
const TAG_LENGTH = 16; // 16 bytes for GCM tag
|
|
7
|
+
/**
|
|
8
|
+
* Decrypts an encrypted config value
|
|
9
|
+
* Based on @quonfig/node/src/encryption.ts
|
|
10
|
+
* @param {string} encryptedString - The encrypted string in format: encryptedData--IV--authTag
|
|
11
|
+
* @param {string} keyStringHex - The encryption key as a 64-character hex string
|
|
12
|
+
* @returns {string} The decrypted plaintext value
|
|
13
|
+
*/
|
|
14
|
+
export function decrypt(encryptedString, keyStringHex) {
|
|
15
|
+
if (keyStringHex.length !== KEY_LENGTH * 2) {
|
|
16
|
+
throw new Error(`Invalid key length. Key must be a ${KEY_LENGTH * 2}-character hex string.`);
|
|
17
|
+
}
|
|
18
|
+
const parts = encryptedString.split(SEPARATOR);
|
|
19
|
+
if (parts.length !== 3) {
|
|
20
|
+
throw new Error('Invalid encrypted string. Must contain encrypted data, IV, and auth tag.');
|
|
21
|
+
}
|
|
22
|
+
const encryptedDataPart = parts[0];
|
|
23
|
+
const ivPart = parts[1];
|
|
24
|
+
const authTagPart = parts[2];
|
|
25
|
+
if (encryptedDataPart === undefined || ivPart === undefined || authTagPart === undefined) {
|
|
26
|
+
throw new Error('Invalid encrypted string. All parts must be defined.');
|
|
27
|
+
}
|
|
28
|
+
if (encryptedDataPart === '') {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
// Convert hex strings to bytes
|
|
32
|
+
const keyBytes = forge.util.hexToBytes(keyStringHex);
|
|
33
|
+
const encryptedBytes = forge.util.hexToBytes(encryptedDataPart);
|
|
34
|
+
const ivBytes = forge.util.hexToBytes(ivPart);
|
|
35
|
+
const tagBytes = forge.util.hexToBytes(authTagPart);
|
|
36
|
+
// Create decipher
|
|
37
|
+
const decipher = forge.cipher.createDecipher('AES-GCM', keyBytes);
|
|
38
|
+
decipher.start({
|
|
39
|
+
iv: ivBytes,
|
|
40
|
+
tag: forge.util.createBuffer(tagBytes),
|
|
41
|
+
tagLength: TAG_LENGTH * 8, // tagLength is in bits
|
|
42
|
+
});
|
|
43
|
+
// Decrypt the data
|
|
44
|
+
decipher.update(forge.util.createBuffer(encryptedBytes));
|
|
45
|
+
const success = decipher.finish();
|
|
46
|
+
if (!success) {
|
|
47
|
+
throw new Error('Authentication tag verification failed');
|
|
48
|
+
}
|
|
49
|
+
return decipher.output.toString();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates an encrypted ConfigValue using the new v1 API endpoints
|
|
53
|
+
* @param command - The APICommand instance for API calls and logging
|
|
54
|
+
* @param value - The plaintext value to encrypt
|
|
55
|
+
* @param secret - Secret configuration containing key name
|
|
56
|
+
* @param environmentId - Environment ID to fetch encryption key for (empty string for default)
|
|
57
|
+
* @returns Result<ConfigValue> with encrypted value or error
|
|
58
|
+
*/
|
|
59
|
+
export async function makeConfidentialValue(command, value, secret, environmentId) {
|
|
60
|
+
// First check if the encryption key exists in metadata
|
|
61
|
+
const metadataRequest = await command.apiClient.post('/api/v1/metadata/list', { workspaceId: command.workspaceId });
|
|
62
|
+
if (!metadataRequest.ok) {
|
|
63
|
+
return failure(`Failed to check if encryption key exists: ${metadataRequest.status}`, {
|
|
64
|
+
phase: 'finding-secret',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const metadata = metadataRequest.json;
|
|
68
|
+
const keyExists = metadata.configs.some((config) => config.key === secret.keyName);
|
|
69
|
+
if (!keyExists) {
|
|
70
|
+
return failure(`Failed to create secret: encryption key '${secret.keyName}' does not exist. Please create it first or use --secret-key-name to specify a different key.`, {
|
|
71
|
+
phase: 'finding-secret',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Fetch the encryption key config
|
|
75
|
+
const configRequest = await command.apiClient.post('/api/v1/metadata/getByKey', { workspaceId: command.workspaceId, key: secret.keyName });
|
|
76
|
+
if (!configRequest.ok) {
|
|
77
|
+
const message = configRequest.status === 404
|
|
78
|
+
? `Failed to create secret: ${secret.keyName} not found`
|
|
79
|
+
: `Failed to fetch encryption key config ${secret.keyName}: ${configRequest.status}`;
|
|
80
|
+
return failure(message, {
|
|
81
|
+
phase: 'finding-secret',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const keyConfig = configRequest.json;
|
|
86
|
+
// Find the encryption key for this environment (or default)
|
|
87
|
+
let secretKey;
|
|
88
|
+
let envVar;
|
|
89
|
+
// Check environment-specific config first
|
|
90
|
+
if (keyConfig.environments && environmentId) {
|
|
91
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
92
|
+
const envConfig = keyConfig.environments.find((env) => String(env.id) === String(environmentId));
|
|
93
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
94
|
+
const ruleValue = envConfig?.rules?.[0]?.value;
|
|
95
|
+
// Check if it's a provided value (env var)
|
|
96
|
+
if (ruleValue?.type === 'provided' && ruleValue?.value?.lookup) {
|
|
97
|
+
envVar = ruleValue.value.lookup;
|
|
98
|
+
}
|
|
99
|
+
// Check for old format: provided.lookup
|
|
100
|
+
else if (ruleValue?.provided?.lookup) {
|
|
101
|
+
envVar = ruleValue.provided.lookup;
|
|
102
|
+
}
|
|
103
|
+
// Otherwise it's a literal value
|
|
104
|
+
else if (ruleValue?.value && typeof ruleValue.value === 'string') {
|
|
105
|
+
secretKey = ruleValue.value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fall back to default config
|
|
109
|
+
if (!secretKey && !envVar) {
|
|
110
|
+
const defaultValue = keyConfig.default?.rules?.[0]?.value;
|
|
111
|
+
// Check if it's a provided value (env var)
|
|
112
|
+
if (defaultValue?.type === 'provided' && defaultValue?.value?.lookup) {
|
|
113
|
+
envVar = defaultValue.value.lookup;
|
|
114
|
+
}
|
|
115
|
+
// Check for old format: provided.lookup
|
|
116
|
+
else if (defaultValue?.provided?.lookup) {
|
|
117
|
+
envVar = defaultValue.provided.lookup;
|
|
118
|
+
}
|
|
119
|
+
// Otherwise it's a literal value
|
|
120
|
+
else if (defaultValue?.value && typeof defaultValue.value === 'string') {
|
|
121
|
+
secretKey = defaultValue.value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// If we have an env var, resolve it
|
|
125
|
+
if (envVar && !secretKey) {
|
|
126
|
+
secretKey = process.env[envVar];
|
|
127
|
+
command.log(`Encrypting with key from env var: ${envVar}`);
|
|
128
|
+
if (typeof secretKey !== 'string') {
|
|
129
|
+
return failure(`Failed to create secret: env var ${envVar} is not present`, {
|
|
130
|
+
phase: 'finding-secret',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// If we have a literal value, use it
|
|
135
|
+
if (secretKey) {
|
|
136
|
+
command.verboseLog(`Using literal value from ${secret.keyName} to encrypt secret`);
|
|
137
|
+
}
|
|
138
|
+
if (!secretKey) {
|
|
139
|
+
return failure(`Failed to create secret: ${secret.keyName} does not have a value configured for environment ${environmentId || 'default'} or default env`, {
|
|
140
|
+
phase: 'finding-secret',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (secretKey.length !== 64) {
|
|
144
|
+
return failure(`Secret key is too short. ${secret.keyName} must be 64 characters.`, {
|
|
145
|
+
phase: 'finding-secret',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return success({
|
|
149
|
+
confidential: true,
|
|
150
|
+
decryptWith: secret.keyName,
|
|
151
|
+
value: encryption.encrypt(value, secretKey),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface CreateFileManagerArgs {
|
|
2
|
+
outputDirectory: string;
|
|
3
|
+
verboseLog: (category: string | unknown, message?: unknown) => void;
|
|
4
|
+
}
|
|
5
|
+
interface FileManager {
|
|
6
|
+
writeFile: (args: {
|
|
7
|
+
data: string;
|
|
8
|
+
filename: string;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createFileManager({ outputDirectory, verboseLog }: CreateFileManagerArgs): FileManager;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export function createFileManager({ outputDirectory, verboseLog }) {
|
|
4
|
+
return {
|
|
5
|
+
async writeFile({ data, filename }) {
|
|
6
|
+
// Ensure the directory exists
|
|
7
|
+
verboseLog(`Creating directory: ${outputDirectory}`);
|
|
8
|
+
await fs.promises.mkdir(outputDirectory, { recursive: true });
|
|
9
|
+
// Write the generated code to the file
|
|
10
|
+
const outputFile = path.join(outputDirectory, filename);
|
|
11
|
+
verboseLog(`Writing file: ${outputFile}`);
|
|
12
|
+
await fs.promises.writeFile(outputFile, data);
|
|
13
|
+
verboseLog(`Wrote file at ${outputFile}`);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|