Compare commits

...

8 Commits

Author SHA1 Message Date
nivedin
9581f1d747 refactor: asterik length same as real text length 2023-09-21 13:19:57 +05:30
nivedin
882aafdb43 refactor: update secret toggle icon 2023-09-21 13:18:50 +05:30
Daniel Maurer
f33fa6ac1a fix: adjusted tests to work with the secret flag 2023-09-19 12:37:31 +05:30
Daniel Maurer
b9a1cc21f1 fix: fixed bug in gui with masked secrets 2023-09-19 12:37:31 +05:30
Daniel Maurer
f530fc2853 fix: inputfield to readonly + asterisk if secret 2023-09-19 12:37:28 +05:30
Daniel Maurer
e0eb8af6f5 feat: added masking of secrets in cli in url 2023-09-19 12:37:13 +05:30
Daniel Maurer
088f1d6b47 feat: missing files from last commit 2023-09-19 12:37:13 +05:30
Daniel Maurer
7d61e69b3d feat: added toggle Button to Frontend 2023-09-19 12:37:10 +05:30
32 changed files with 387 additions and 120 deletions

View File

@@ -14,9 +14,10 @@ import { isHoppCLIError } from "../utils/checks";
export const test = (path: string, options: TestCmdOptions) => async () => {
try {
const delay = options.delay ? parseDelayOption(options.delay) : 0
const envs = options.env ? await parseEnvsData(options.env) : <HoppEnvs>{ global: [], selected: [] }
const envName = options.envName
const envs = options.env ? await parseEnvsData(options.env, envName) : <HoppEnvs>{ global: [], selected: [] }
const collections = await parseCollectionData(path)
const report = await collectionsRunner({collections, envs, delay})
const hasSucceeded = collectionsRunnerResult(report)
collectionsRunnerExit(hasSucceeded)

View File

@@ -51,6 +51,9 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
case "MALFORMED_COLLECTION":
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
break;
case "ENVIRONMENT_NAME_NOT_FOUND":
ERROR_MSG = `\n${parseErrorData(error.data)}`;
break;
case "NO_FILE_PATH":
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
break;

View File

@@ -50,6 +50,7 @@ program
"path to a hoppscotch collection.json file for CI testing"
)
.option("-e, --env <file_path>", "path to an environment variables json file")
.option("-eN, --envName <environment_name>","Specific Name of the environment")
.option(
"-d, --delay <delay_in_ms>",
"delay in milliseconds(ms) between consecutive requests within a collection"

View File

@@ -33,4 +33,5 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
effectiveFinalParams: { key: string; value: string; active: boolean }[];
effectiveFinalBody: FormData | string | null;
effectiveFinalMaskedURL: string;
}

View File

@@ -5,23 +5,46 @@ import { readJsonFile } from "../../utils/mutators";
/**
* Parses env json file for given path and validates the parsed env json object.
* @param path Path of env.json file to be parsed.
* @param envName Name of the environment that should be used. If undefined first environment is used.
* @returns For successful parsing we get HoppEnvs object.
*/
export async function parseEnvsData(path: string) {
export async function parseEnvsData(path: string, envName: string | undefined) {
const contents = await readJsonFile(path)
if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
if(!(contents && typeof contents === "object" && Array.isArray(contents))) {
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
}
const envPairs: Array<HoppEnvPair> = []
for( const [key,value] of Object.entries(contents)) {
if(typeof value !== "string") {
throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
}
const contentEntries = Object.entries(contents)
let environmentFound = false;
envPairs.push({key, value})
for(const [key, obj] of contentEntries) {
if(!(typeof obj === "object" && "name" in obj && "variables" in obj)) {
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: obj } })
}
if(envName && envName !== obj.name) {
continue
}
environmentFound = true;
for(const variable of obj.variables) {
if(
!(
typeof variable === "object" &&
"key" in variable &&
"value" in variable &&
"secret" in variable
)
) {
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: variable } });
}
const { key, value, secret } = variable;
envPairs.push({ key: key, value: value, secret: secret });
}
break
}
if(envName && !environmentFound) {
throw error({ code: "ENVIRONMENT_NAME_NOT_FOUND", data: envName });
}
return <HoppEnvs>{ global: [], selected: envPairs }
}

View File

@@ -1,6 +1,7 @@
export type TestCmdOptions = {
env: string | undefined;
delay: string | undefined;
envName: string | undefined;
};
export type HOPP_ENV_FILE_EXT = "json";

View File

@@ -15,6 +15,7 @@ type HoppErrors = {
FILE_NOT_FOUND: HoppErrorPath;
UNKNOWN_COMMAND: HoppErrorCmd;
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
ENVIRONMENT_NAME_NOT_FOUND: HoppErrorData;
NO_FILE_PATH: {};
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
PARSING_ERROR: HoppErrorData;

View File

@@ -7,7 +7,7 @@ export type FormDataEntry = {
value: string | Blob;
};
export type HoppEnvPair = { key: string; value: string };
export type HoppEnvPair = { key: string; value: string; secret: boolean };
export type HoppEnvs = {
global: HoppEnvPair[];

View File

@@ -1,7 +1,7 @@
import { bold } from "chalk";
import { groupEnd, group, log } from "console";
import { handleError } from "../handlers/error";
import { RequestConfig } from "../interfaces/request";
import { Method } from "axios";
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
import { HoppCLIError } from "../types/errors";
import {
@@ -172,11 +172,12 @@ export const printFailedTestsReport = (
export const printRequestRunner = {
/**
* Request-runner starting message.
* @param requestConfig Provides request's method and url.
* @param requestMethod Provides request's method
* @param maskedURL Provides the URL with secrets masked with asterisks
*/
start: (requestConfig: RequestConfig) => {
const METHOD = BG_INFO(` ${requestConfig.method} `);
const ENDPOINT = requestConfig.url;
start: (requestMethod: Method | undefined, maskedURL: string) => {
const METHOD = BG_INFO(` ${requestMethod} `);
const ENDPOINT = maskedURL;
process.stdout.write(`${METHOD} ${ENDPOINT}`);
},

View File

@@ -50,9 +50,9 @@ export const preRequestScriptRunner = (
isHoppCLIError(reason)
? reason
: error({
code: "PRE_REQUEST_SCRIPT_ERROR",
data: reason,
})
code: "PRE_REQUEST_SCRIPT_ERROR",
data: reason,
})
)
);
@@ -151,6 +151,12 @@ export function getEffectiveRESTRequest(
request.endpoint,
envVariables
);
const maskedEnvVariables = setAllEnvironmentValuesToAsterisk(envVariables)
const _effectiveFinalMaskedURL = parseTemplateStringE(
request.endpoint,
maskedEnvVariables)
if (E.isLeft(_effectiveFinalURL)) {
return E.left(
error({
@@ -160,6 +166,7 @@ export function getEffectiveRESTRequest(
);
}
const effectiveFinalURL = _effectiveFinalURL.right;
const effectiveFinalMaskedURL = E.isLeft(_effectiveFinalMaskedURL) ? request.endpoint : _effectiveFinalMaskedURL.right;
return E.right({
...request,
@@ -167,6 +174,7 @@ export function getEffectiveRESTRequest(
effectiveFinalHeaders,
effectiveFinalParams,
effectiveFinalBody,
effectiveFinalMaskedURL,
});
}
@@ -242,15 +250,15 @@ function getFinalBodyFromRequest(
arrayFlatMap((x) =>
x.isFile
? x.value.map((v) => ({
key: parseTemplateString(x.key, envVariables),
value: v as string | Blob,
}))
key: parseTemplateString(x.key, envVariables),
value: v as string | Blob,
}))
: [
{
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
},
]
{
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
},
]
),
toFormData,
E.right
@@ -287,3 +295,22 @@ export const getPreRequestMetrics = (
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
(scripts) => <PreRequestMetrics>{ scripts, duration }
);
/**
* Mask all environment values with asterisks
* @param variables Environment variable array
* @returns Environment variable array with masked values
*/
const setAllEnvironmentValuesToAsterisk = (
variables: Environment["variables"]
): Environment["variables"] => {
const envVariables: Environment["variables"] = [];
for (const variable of variables) {
let value = variable.value
if (variable.secret) {
value = "******"
}
envVariables.push({ key: variable.key, secret: variable.secret, value: value })
}
return envVariables
}

View File

@@ -220,6 +220,7 @@ export const processRequest =
effectiveFinalHeaders: [],
effectiveFinalParams: [],
effectiveFinalURL: "",
effectiveFinalMaskedURL:"",
};
// Executing pre-request-script
@@ -237,8 +238,8 @@ export const processRequest =
// Creating request-config for request-runner.
const requestConfig = createRequest(effectiveRequest);
printRequestRunner.start(requestConfig);
printRequestRunner.start(requestConfig.method, effectiveRequest.effectiveFinalMaskedURL);
// Default value for request-runner's response.
let _requestRunnerRes: RequestRunnerResponse = {

View File

@@ -48,7 +48,8 @@
"turn_off": "Ausschalten",
"turn_on": "Einschalten",
"undo": "Rückgängig machen",
"yes": "Ja"
"yes": "Ja",
"secret": "Als Secret speichern"
},
"add": {
"new": "Neue hinzufügen",
@@ -880,4 +881,4 @@
"team": "Team Workspace",
"title": "Workspaces"
}
}
}

View File

@@ -48,7 +48,8 @@
"turn_off": "Turn off",
"turn_on": "Turn on",
"undo": "Undo",
"yes": "Yes"
"yes": "Yes",
"secret": "Save as secret"
},
"add": {
"new": "Add new",

View File

@@ -96,7 +96,6 @@ declare module 'vue' {
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
@@ -143,7 +142,6 @@ declare module 'vue' {
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
@@ -153,10 +151,8 @@ declare module 'vue' {
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']

View File

@@ -60,7 +60,17 @@
:placeholder="`${t('count.value', { count: index + 1 })}`"
:envs="liveEnvs"
:name="'value' + index"
:secret="env.secret"
/>
<div class="flex">
<HoppButtonSecondary
id="variable"
v-tippy="{ theme: 'tooltip' }"
:title="t('action.secret')"
:icon="env.secret ? IconEyeOff : IconEye"
@click="toggleEnvironmentSecret(index)"
/>
</div>
<div class="flex">
<HoppButtonSecondary
id="variable"
@@ -110,6 +120,8 @@ import IconTrash2 from "~icons/lucide/trash-2"
import IconDone from "~icons/lucide/check"
import IconPlus from "~icons/lucide/plus"
import IconTrash from "~icons/lucide/trash"
import IconEye from "~icons/lucide/eye"
import IconEyeOff from "~icons/lucide/eye-off"
import { clone } from "lodash-es"
import { computed, ref, watch } from "vue"
import * as E from "fp-ts/Either"
@@ -140,6 +152,7 @@ type EnvironmentVariable = {
env: {
key: string
value: string
secret: boolean
}
}
@@ -172,7 +185,7 @@ const idTicker = ref(0)
const editingName = ref<string | null>(null)
const vars = ref<EnvironmentVariable[]>([
{ id: idTicker.value++, env: { key: "", value: "" } },
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
])
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
@@ -203,6 +216,8 @@ const workingEnv = computed(() => {
}
})
const oldEnvironments = ref<string[]>([])
const envList = useReadonlyStream(environments$, []) || props.envVars()
const evnExpandError = computed(() => {
@@ -234,6 +249,28 @@ const liveEnvs = computed(() => {
}
})
watch(liveEnvs, (newLiveEnv, oldLiveEnv) => {
const oldEnvLength = oldLiveEnv.length
const newEnvLength = newLiveEnv.length
if (oldEnvLength === newEnvLength) {
const _oldEnvironments = []
for (let i = 0; i < newEnvLength; i++) {
const newVar = newLiveEnv[i]
const oldVar = oldLiveEnv[i]
let newValue = ""
if (!newVar.secret) {
newValue = newVar.value
} else if (!oldVar.secret) {
newValue = oldVar.value
} else {
newValue = oldEnvironments.value[i]
}
_oldEnvironments.push(newValue)
}
oldEnvironments.value = _oldEnvironments
}
})
watch(
() => props.show,
(show) => {
@@ -262,6 +299,7 @@ const addEnvironmentVariable = () => {
env: {
key: "",
value: "",
secret: false,
},
})
}
@@ -270,12 +308,23 @@ const removeEnvironmentVariable = (index: number) => {
vars.value.splice(index, 1)
}
const toggleEnvironmentSecret = (index: number) => {
vars.value[index].env.secret = !vars.value[index].env.secret
}
const saveEnvironment = () => {
if (!editingName.value) {
toast.error(`${t("environment.invalid_name")}`)
return
}
const _vars = vars.value
for (let i = 0; i < vars.value.length; i++) {
const value = oldEnvironments.value[i]
if (value) {
_vars[i].env.value = value
}
}
vars.value = _vars
const filterdVariables = pipe(
vars.value,
A.filterMap(

View File

@@ -63,7 +63,17 @@
:envs="liveEnvs"
:name="'value' + index"
:readonly="isViewer"
:secret="env.secret"
/>
<div v-if="!isViewer" class="flex">
<HoppButtonSecondary
id="variable"
v-tippy="{ theme: 'tooltip' }"
:title="t('action.secret')"
:icon="env.secret ? IconEyeOff : IconEye"
@click="toggleEnvironmentSecret(index)"
/>
</div>
<div v-if="!isViewer" class="flex">
<HoppButtonSecondary
id="variable"
@@ -139,6 +149,8 @@ import IconTrash from "~icons/lucide/trash"
import IconTrash2 from "~icons/lucide/trash-2"
import IconDone from "~icons/lucide/check"
import IconPlus from "~icons/lucide/plus"
import IconEye from "~icons/lucide/eye"
import IconEyeOff from "~icons/lucide/eye-off"
import { platform } from "~/platform"
type EnvironmentVariable = {
@@ -146,6 +158,7 @@ type EnvironmentVariable = {
env: {
key: string
value: string
secret: boolean
}
}
@@ -182,9 +195,10 @@ const idTicker = ref(0)
const editingName = ref<string | null>(null)
const vars = ref<EnvironmentVariable[]>([
{ id: idTicker.value++, env: { key: "", value: "" } },
{ id: idTicker.value++, env: { key: "", value: "", secret: false } },
])
const oldEnvironments = ref<string[]>([])
const clearIcon = refAutoReset<typeof IconTrash2 | typeof IconDone>(
IconTrash2,
1000
@@ -212,6 +226,28 @@ const liveEnvs = computed(() => {
}
})
watch(liveEnvs, (newLiveEnv, oldLiveEnv) => {
const oldEnvLength = oldLiveEnv.length
const newEnvLength = newLiveEnv.length
if (oldEnvLength === newEnvLength) {
const _oldEnvironments = []
for (let i = 0; i < newEnvLength; i++) {
const newVar = newLiveEnv[i]
const oldVar = oldLiveEnv[i]
let newValue = ""
if (!newVar.secret) {
newValue = newVar.value
} else if (!oldVar.secret) {
newValue = oldVar.value
} else {
newValue = oldEnvironments.value[i]
}
_oldEnvironments.push(newValue)
}
oldEnvironments.value = _oldEnvironments
}
})
watch(
() => props.show,
(show) => {
@@ -220,7 +256,7 @@ watch(
editingName.value = null
vars.value = pipe(
props.envVars() ?? [],
A.map((e: { key: string; value: string }) => ({
A.map((e: { key: string; value: string; secret: boolean }) => ({
id: idTicker.value++,
env: clone(e),
}))
@@ -229,7 +265,7 @@ watch(
editingName.value = props.editingEnvironment.environment.name ?? null
vars.value = pipe(
props.editingEnvironment.environment.variables ?? [],
A.map((e: { key: string; value: string }) => ({
A.map((e: { key: string; value: string; secret: boolean }) => ({
id: idTicker.value++,
env: clone(e),
}))
@@ -251,6 +287,7 @@ const addEnvironmentVariable = () => {
env: {
key: "",
value: "",
secret: false,
},
})
}
@@ -259,6 +296,10 @@ const removeEnvironmentVariable = (index: number) => {
vars.value.splice(index, 1)
}
const toggleEnvironmentSecret = (index: number) => {
vars.value[index].env.secret = !vars.value[index].env.secret
}
const isLoading = ref(false)
const saveEnvironment = async () => {
@@ -269,6 +310,15 @@ const saveEnvironment = async () => {
return
}
const _vars = vars.value
for (let i = 0; i < vars.value.length; i++) {
const value = oldEnvironments.value[i]
if (value) {
_vars[i].env.value = value
}
}
vars.value = _vars
const filterdVariables = pipe(
vars.value,
A.filterMap(

View File

@@ -279,6 +279,7 @@ const globalEnvVars = useReadonlyStream(globalEnv$, []) as Ref<
Array<{
key: string
value: string
secret: boolean
}>
>

View File

@@ -48,6 +48,7 @@ type Props = {
env: {
key: string
value: string
secret: boolean
previousValue?: string
}
status: Status

View File

@@ -55,7 +55,12 @@ import {
keymap,
tooltips,
} from "@codemirror/view"
import { EditorSelection, EditorState, Extension } from "@codemirror/state"
import {
EditorSelection,
EditorState,
Extension,
StateEffect,
} from "@codemirror/state"
import { clone } from "lodash-es"
import { history, historyKeymap } from "@codemirror/commands"
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
@@ -72,13 +77,17 @@ const props = withDefaults(
modelValue?: string
placeholder?: string
styles?: string
envs?: { key: string; value: string; source: string }[] | null
envs?:
| { key: string; value: string; secret: boolean; source: string }[]
| null
focus?: boolean
selectTextOnMount?: boolean
environmentHighlights?: boolean
readonly?: boolean
autoCompleteSource?: string[]
inspectionResults?: InspectorResult[] | undefined
defaultValue?: string
secret?: boolean
}>(),
{
modelValue: "",
@@ -91,6 +100,8 @@ const props = withDefaults(
autoCompleteSource: undefined,
inspectionResult: undefined,
inspectionResults: undefined,
defaultValue: "",
secret: false,
}
)
@@ -104,6 +115,8 @@ const emit = defineEmits<{
(e: "click", ev: any): void
}>()
const asterikedText = computed(() => "*".repeat(props.modelValue.length))
const cachedValue = ref(props.modelValue)
const view = ref<EditorView>()
@@ -263,6 +276,22 @@ watch(
}
)
const prevModelValue = ref(props.modelValue)
watch(
() => props.secret,
(newValue) => {
let visibleValue = asterikedText.value
if (!newValue) {
visibleValue = prevModelValue.value
} else {
prevModelValue.value = props.modelValue
}
emit("update:modelValue", visibleValue)
updateEditorViewTheme(newValue)
}
)
/**
* Used to scroll the active suggestion into view
*/
@@ -284,7 +313,6 @@ watch(
() => props.modelValue,
(newVal) => {
const singleLinedText = newVal.replaceAll("\n", "")
const currDoc = view.value?.state.doc
.toJSON()
.join(view.value.state.lineBreak)
@@ -320,6 +348,7 @@ const envVars = computed(() =>
? props.envs.map((x) => ({
key: x.key,
value: x.value,
secret: x.secret,
sourceEnv: x.source,
}))
: aggregateEnvs.value
@@ -354,6 +383,14 @@ function handleTextSelection() {
}
}
}
const updateEditorViewTheme = (readonly: boolean) => {
if (view.value) {
const extensions: Extension = getExtensions(readonly)
view.value.dispatch({
effects: [StateEffect.reconfigure.of(extensions)],
})
}
}
const initView = (el: any) => {
// Debounce to prevent double click from selecting the word
@@ -364,17 +401,31 @@ const initView = (el: any) => {
el.addEventListener("mouseup", debounceFn)
el.addEventListener("keyup", debounceFn)
if (props.secret) {
emit("update:modelValue", asterikedText.value)
}
const extensions: Extension = getExtensions(props.readonly || props.secret)
view.value = new EditorView({
parent: el,
state: EditorState.create({
doc: props.modelValue,
extensions,
}),
})
}
const getExtensions = (readonly: boolean): Extension => {
const extensions: Extension = [
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
EditorView.updateListener.of((update) => {
if (props.readonly) {
if (readonly) {
update.view.contentDOM.inputMode = "none"
}
}),
EditorState.changeFilter.of(() => !props.readonly),
EditorState.changeFilter.of(() => !readonly),
inputTheme,
props.readonly
readonly
? EditorView.theme({
".cm-content": {
caretColor: "var(--secondary-dark-color)",
@@ -406,7 +457,7 @@ const initView = (el: any) => {
ViewPlugin.fromClass(
class {
update(update: ViewUpdate) {
if (props.readonly) return
if (readonly) return
if (update.docChanged) {
const prevValue = clone(cachedValue.value)
@@ -418,9 +469,14 @@ const initView = (el: any) => {
// We do not update the cache directly in this case (to trigger value watcher to dispatch)
// So, we desync cachedValue a bit so we can trigger updates
const value = clone(cachedValue.value).replaceAll("\n", "")
emit("update:modelValue", value)
emit("change", value)
if (props.secret) {
const asterikedValue = "*".repeat(value.length)
emit("update:modelValue", asterikedValue)
emit("change", asterikedValue)
} else {
emit("update:modelValue", value)
emit("change", value)
}
const pasted = !!update.transactions.find((txn) =>
txn.isUserEvent("input.paste")
@@ -445,14 +501,7 @@ const initView = (el: any) => {
history(),
keymap.of([...historyKeymap]),
]
view.value = new EditorView({
parent: el,
state: EditorState.create({
doc: props.modelValue,
extensions,
}),
})
return extensions
}
const triggerTextSelection = () => {

View File

@@ -66,7 +66,10 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
const envValue = tooltipEnv?.value ?? "Not found"
let envValue = tooltipEnv?.value ?? "Not found"
if (tooltipEnv?.secret) {
envValue = "******"
}
const result = parseTemplateStringE(envValue, aggregateEnvs)

View File

@@ -2,7 +2,7 @@
"!name": "pw-pre",
"pw": {
"env": {
"set": "fn(key: string, value: string)",
"set": "fn(key: string, value: string, secret: boolean)",
"get": "fn(key: string) -> string",
"getResolve": "fn(key: string) -> string",
"resolve": "fn(value: string) -> string"

View File

@@ -21,7 +21,7 @@
"body": "?"
},
"env": {
"set": "fn(key: string, value: string)",
"set": "fn(key: string, value: string, secret:boolean)",
"get": "fn(key: string) -> string",
"getResolve": "fn(key: string) -> string",
"resolve": "fn(value: string) -> string"

View File

@@ -186,14 +186,19 @@ const dispatchers = defineDispatchers({
},
addEnvironmentVariable(
{ environments }: EnvironmentStore,
{ envIndex, key, value }: { envIndex: number; key: string; value: string }
{
envIndex,
key,
value,
secret,
}: { envIndex: number; key: string; value: string; secret: boolean }
) {
return {
environments: environments.map((env, index) =>
index === envIndex
? {
...env,
variables: [...env.variables, { key, value }],
variables: [...env.variables, { key, value, secret }],
}
: env
),
@@ -221,7 +226,10 @@ const dispatchers = defineDispatchers({
{
envIndex,
vars,
}: { envIndex: number; vars: { key: string; value: string }[] }
}: {
envIndex: number
vars: { key: string; value: string; secret: boolean }[]
}
) {
return {
environments: environments.map((env, index) =>
@@ -241,11 +249,13 @@ const dispatchers = defineDispatchers({
variableIndex,
updatedKey,
updatedValue,
updatedSecret,
}: {
envIndex: number
variableIndex: number
updatedKey: string
updatedValue: string
updatedSecret: boolean
}
) {
return {
@@ -255,7 +265,11 @@ const dispatchers = defineDispatchers({
...env,
variables: env.variables.map((v, vIndex) =>
vIndex === variableIndex
? { key: updatedKey, value: updatedValue }
? {
key: updatedKey,
value: updatedValue,
secret: updatedSecret,
}
: v
),
}
@@ -359,6 +373,7 @@ export const currentEnvironment$: Observable<Environment | undefined> =
export type AggregateEnvironment = {
key: string
value: string
secret: boolean
sourceEnv: string
}
@@ -373,11 +388,11 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
map(([selectedEnv, globalVars]) => {
const results: AggregateEnvironment[] = []
selectedEnv?.variables.forEach(({ key, value }) =>
results.push({ key, value, sourceEnv: selectedEnv.name })
selectedEnv?.variables.forEach(({ key, value, secret }) =>
results.push({ key, value, secret, sourceEnv: selectedEnv.name })
)
globalVars.forEach(({ key, value }) =>
results.push({ key, value, sourceEnv: "Global" })
globalVars.forEach(({ key, value, secret }) =>
results.push({ key, value, secret, sourceEnv: "Global" })
)
return results
@@ -593,7 +608,7 @@ export function updateEnvironment(envIndex: number, updatedEnv: Environment) {
export function setEnvironmentVariables(
envIndex: number,
vars: { key: string; value: string }[]
vars: { key: string; value: string; secret: boolean }[]
) {
environmentsStore.dispatch({
dispatcher: "setEnvironmentVariables",
@@ -606,7 +621,7 @@ export function setEnvironmentVariables(
export function addEnvironmentVariable(
envIndex: number,
{ key, value }: { key: string; value: string }
{ key, value, secret }: { key: string; value: string; secret: boolean }
) {
environmentsStore.dispatch({
dispatcher: "addEnvironmentVariable",
@@ -614,6 +629,7 @@ export function addEnvironmentVariable(
envIndex,
key,
value,
secret,
},
})
}
@@ -634,7 +650,7 @@ export function removeEnvironmentVariable(
export function updateEnvironmentVariable(
envIndex: number,
variableIndex: number,
{ key, value }: { key: string; value: string }
{ key, value, secret }: { key: string; value: string; secret: boolean }
) {
environmentsStore.dispatch({
dispatcher: "updateEnvironmentVariable",
@@ -643,6 +659,7 @@ export function updateEnvironmentVariable(
variableIndex,
updatedKey: key,
updatedValue: value,
updatedSecret: secret,
},
})
}

View File

@@ -7,6 +7,7 @@ export type Environment = {
variables: {
key: string
value: string
secret: boolean
}[]
}

View File

@@ -11,16 +11,16 @@ describe("execPreRequestScript", () => {
{
global: [],
selected: [
{ key: "bob", value: "oldbob" },
{ key: "foo", value: "bar" },
{ key: "bob", value: "oldbob", secret: false },
{ key: "foo", value: "bar", secret: false },
],
}
)()
).resolves.toEqualRight({
global: [],
selected: [
{ key: "bob", value: "newbob" },
{ key: "foo", value: "bar" },
{ key: "bob", value: "newbob", secret: false },
{ key: "foo", value: "bar", secret: false },
],
})
})
@@ -34,8 +34,8 @@ describe("execPreRequestScript", () => {
{
global: [],
selected: [
{ key: "bob", value: "oldbob" },
{ key: "foo", value: "bar" },
{ key: "bob", value: "oldbob", secret: false },
{ key: "foo", value: "bar", secret: false },
],
}
)()
@@ -51,8 +51,8 @@ describe("execPreRequestScript", () => {
{
global: [],
selected: [
{ key: "bob", value: "oldbob" },
{ key: "foo", value: "bar" },
{ key: "bob", value: "oldbob", secret: false },
{ key: "foo", value: "bar", secret: false },
],
}
)()
@@ -68,8 +68,8 @@ describe("execPreRequestScript", () => {
{
global: [],
selected: [
{ key: "bob", value: "oldbob" },
{ key: "foo", value: "bar" },
{ key: "bob", value: "oldbob", secret: false },
{ key: "foo", value: "bar", secret: false },
],
}
)()
@@ -86,7 +86,7 @@ describe("execPreRequestScript", () => {
)()
).resolves.toEqualRight({
global: [],
selected: [{ key: "foo", value: "bar" }],
selected: [{ key: "foo", value: "bar", secret: false }],
})
})
})

View File

@@ -30,6 +30,7 @@ describe("pw.env.get", () => {
{
key: "a",
value: "b",
secret: false,
},
],
}
@@ -58,6 +59,7 @@ describe("pw.env.get", () => {
{
key: "a",
value: "b",
secret: false,
},
],
selected: [],
@@ -111,12 +113,14 @@ describe("pw.env.get", () => {
{
key: "a",
value: "global val",
secret: false,
},
],
selected: [
{
key: "a",
value: "selected val",
secret: false,
},
],
}
@@ -146,6 +150,7 @@ describe("pw.env.get", () => {
{
key: "a",
value: "<<hello>>",
secret: false,
},
],
}

View File

@@ -30,6 +30,7 @@ describe("pw.env.getResolve", () => {
{
key: "a",
value: "b",
secret: false,
},
],
}
@@ -58,6 +59,7 @@ describe("pw.env.getResolve", () => {
{
key: "a",
value: "b",
secret: false,
},
],
selected: [],
@@ -111,12 +113,14 @@ describe("pw.env.getResolve", () => {
{
key: "a",
value: "global val",
secret: false,
},
],
selected: [
{
key: "a",
value: "selected val",
secret: false,
},
],
}
@@ -146,10 +150,12 @@ describe("pw.env.getResolve", () => {
{
key: "a",
value: "<<hello>>",
secret: false,
},
{
key: "hello",
value: "there",
secret: false,
},
],
}
@@ -179,10 +185,12 @@ describe("pw.env.getResolve", () => {
{
key: "a",
value: "<<hello>>",
secret: false,
},
{
key: "hello",
value: "<<a>>",
secret: false,
},
],
}

View File

@@ -41,6 +41,7 @@ describe("pw.env.resolve", () => {
{
key: "hello",
value: "there",
secret: false,
},
],
selected: [],
@@ -71,6 +72,7 @@ describe("pw.env.resolve", () => {
{
key: "hello",
value: "there",
secret: false,
},
],
}
@@ -99,12 +101,14 @@ describe("pw.env.resolve", () => {
{
key: "hello",
value: "yo",
secret: false,
},
],
selected: [
{
key: "hello",
value: "there",
secret: false,
},
],
}
@@ -134,10 +138,12 @@ describe("pw.env.resolve", () => {
{
key: "hello",
value: "<<there>>",
secret: false,
},
{
key: "there",
value: "<<hello>>",
secret: false,
},
],
}

View File

@@ -33,6 +33,7 @@ describe("pw.env.set", () => {
{
key: "a",
value: "b",
secret: false,
},
],
}
@@ -43,6 +44,7 @@ describe("pw.env.set", () => {
{
key: "a",
value: "c",
secret: false,
},
],
})
@@ -60,6 +62,7 @@ describe("pw.env.set", () => {
{
key: "a",
value: "b",
secret: false,
},
],
selected: [],
@@ -71,6 +74,7 @@ describe("pw.env.set", () => {
{
key: "a",
value: "c",
secret: false,
},
],
})
@@ -88,12 +92,14 @@ describe("pw.env.set", () => {
{
key: "a",
value: "b",
secret: false,
},
],
selected: [
{
key: "a",
value: "d",
secret: false,
},
],
}
@@ -104,12 +110,14 @@ describe("pw.env.set", () => {
{
key: "a",
value: "b",
secret: false,
},
],
selected: [
{
key: "a",
value: "c",
secret: false,
},
],
})
@@ -134,6 +142,7 @@ describe("pw.env.set", () => {
{
key: "a",
value: "c",
secret: false,
},
],
})

View File

@@ -87,28 +87,32 @@ export const execPreRequestScript = (
}
})
const envSetHandle = vm.newFunction("set", (keyHandle, valueHandle) => {
const key: unknown = vm.dump(keyHandle)
const value: unknown = vm.dump(valueHandle)
const envSetHandle = vm.newFunction(
"set",
(keyHandle, valueHandle, secretHandle) => {
const key: unknown = vm.dump(keyHandle)
const value: unknown = vm.dump(valueHandle)
const secret: boolean = vm.dump(secretHandle)
if (typeof key !== "string") {
return {
error: vm.newString("Expected key to be a string"),
}
}
if (typeof value !== "string") {
return {
error: vm.newString("Expected value to be a string"),
}
}
currentEnvs = setEnv(key, value, secret, currentEnvs)
if (typeof key !== "string") {
return {
error: vm.newString("Expected key to be a string"),
value: vm.undefined,
}
}
if (typeof value !== "string") {
return {
error: vm.newString("Expected value to be a string"),
}
}
currentEnvs = setEnv(key, value, currentEnvs)
return {
value: vm.undefined,
}
})
)
const envResolveHandle = vm.newFunction("resolve", (valueHandle) => {
const value: unknown = vm.dump(valueHandle)

View File

@@ -527,28 +527,32 @@ export const execTestScript = (
}
)
const envSetHandle = vm.newFunction("set", (keyHandle, valueHandle) => {
const key: unknown = vm.dump(keyHandle)
const value: unknown = vm.dump(valueHandle)
const envSetHandle = vm.newFunction(
"set",
(keyHandle, valueHandle, secretHandle) => {
const key: unknown = vm.dump(keyHandle)
const value: unknown = vm.dump(valueHandle)
const secret: boolean = vm.dump(secretHandle)
if (typeof key !== "string") {
return {
error: vm.newString("Expected key to be a string"),
}
}
if (typeof value !== "string") {
return {
error: vm.newString("Expected value to be a string"),
}
}
currentEnvs = setEnv(key, value, secret, currentEnvs)
if (typeof key !== "string") {
return {
error: vm.newString("Expected key to be a string"),
value: vm.undefined,
}
}
if (typeof value !== "string") {
return {
error: vm.newString("Expected value to be a string"),
}
}
currentEnvs = setEnv(key, value, currentEnvs)
return {
value: vm.undefined,
}
})
)
const envResolveHandle = vm.newFunction("resolve", (valueHandle) => {
const value: unknown = vm.dump(valueHandle)

View File

@@ -50,6 +50,7 @@ export function getEnv(envName: string, envs: TestResult["envs"]) {
export function setEnv(
envName: string,
envValue: string,
envSecret: boolean,
envs: TestResult["envs"]
): TestResult["envs"] {
const indexInSelected = envs.selected.findIndex((x) => x.key === envName)
@@ -80,6 +81,7 @@ export function setEnv(
envs.selected.push({
key: envName,
value: envValue,
secret: envSecret,
})
return {