commit 3263d18f525ac458a0914119a5e9174480616ae1
parent 7e7395c20506a4e48cdbcf705a4752bf7f26dc88
Author: Sebastian <sebasjm@gmail.com>
Date: Mon, 14 Apr 2025 23:41:31 -0300
fix kyc spa: accept-tos form
Diffstat:
13 files changed, 308 insertions(+), 115 deletions(-)
diff --git a/packages/challenger-ui/build.mjs b/packages/challenger-ui/build.mjs
@@ -19,6 +19,7 @@ import { build } from "@gnu-taler/web-util/build";
await build({
type: "production",
+ importMeta: import.meta,
source: {
js: ["src/main.js","src/index.tsx"],
assets: [{
diff --git a/packages/kyc-ui/build.mjs b/packages/kyc-ui/build.mjs
@@ -19,6 +19,7 @@ import { build } from "@gnu-taler/web-util/build";
await build({
type: "production",
+ importMeta: import.meta,
source: {
js: ["src/index.tsx"],
assets: [{
diff --git a/packages/kyc-ui/src/pages/FillForm.tsx b/packages/kyc-ui/src/pages/FillForm.tsx
@@ -23,12 +23,16 @@ import {
assertUnreachable,
} from "@gnu-taler/taler-util";
import {
+ AcceptTermOfServiceContext,
+ Attention,
Button,
ErrorsSummary,
FormMetadata,
FormUI,
InternationalizationAPI,
+ Loading,
LocalNotificationBanner,
+ useAsyncAsHook,
useExchangeApiContext,
useForm,
useLocalNotificationHandler,
@@ -39,6 +43,7 @@ import { usePreferences } from "../context/preferences.js";
import { useUiFormsContext } from "../context/ui-forms.js";
import { preloadedForms } from "../forms/index.js";
import { TalerFormAttributes } from "@gnu-taler/taler-util";
+import { KycRequirementInformationId } from "@gnu-taler/taler-util";
const TALER_SCREEN_ID = 103;
@@ -66,37 +71,60 @@ type KycForm = {
payload: object;
};
-export function FillForm({
- token,
+async function getContextByFormId(
+ id: string,
+ requirement: KycRequirementInformation,
+): Promise<object> {
+ if (id === "accept-tos") {
+ const reqContx: any = requirement.context;
+ if (!reqContx) {
+ throw Error(
+ "accept-tos form requires context with 'tos_url' property. No context present.",
+ );
+ }
+ const tos_url = reqContx["tos_url"];
+ if (!tos_url) {
+ throw Error(
+ "accept-tos form requires context with 'tos_url' property. No URL present.",
+ );
+ }
+
+ const resp = await fetch(tos_url);
+ const tosVersion = resp.headers.get("Taler-Terms-Version");
+
+ if (!tosVersion) {
+ throw Error(
+ "accept-tos form requires 'Taler-Terms-Version' in request response to the 'tos_url'",
+ );
+ }
+
+ const ctx: AcceptTermOfServiceContext = {
+ tos_url,
+ tosVersion,
+ expiration_time: reqContx["expiration_time"],
+ provider_name: reqContx["provider_name"],
+ successor_measure: reqContx["successor_measure"],
+ };
+ return ctx;
+ }
+ return requirement.context ?? {};
+}
+
+function ShowForm({
+ theForm,
formId,
- requirement,
+ reqId,
onComplete,
-}: Props): VNode {
- const { i18n } = useTranslationContext();
+}: {
+ theForm: FormMetadata;
+ formId: string;
+ reqId: KycRequirementInformationId;
+ onComplete: () => void;
+}): VNode {
const { lib } = useExchangeApiContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
const [preferences] = usePreferences();
-
- const customForm =
- requirement.context && "form" in requirement.context
- ? ({
- id: (requirement.context.form as any).id,
- config: requirement.context.form,
- label: "Officer defined form",
- version: 1,
- } as FormMetadata)
- : undefined;
-
- const { forms } = useUiFormsContext();
- const allForms = customForm ? [...forms, customForm] : forms;
- const theForm = searchForm(i18n, allForms, formId, requirement.context);
- const reqId = requirement.id;
- if (!theForm) {
- return <div>form with id {formId} not found</div>;
- }
- if (!reqId) {
- return <div>no id for this form, can't upload</div>;
- }
+ const { i18n } = useTranslationContext();
const { model: handler, status } = useForm<FormType>(theForm.config, {});
const validatedForm = status.status !== "ok" ? undefined : status.result;
@@ -107,8 +135,8 @@ export function FillForm({
: withErrorHandler(
async () => {
// FIXME: remove this one after https://bugs.gnunet.org/view.php?id=9715 is closed
- validatedForm.form_id = formId
- validatedForm[TalerFormAttributes.FORM_ID] = formId
+ validatedForm.form_id = formId;
+ validatedForm[TalerFormAttributes.FORM_ID] = formId;
return lib.exchange.uploadKycForm(reqId, validatedForm);
},
(res) => {
@@ -170,6 +198,61 @@ export function FillForm({
);
}
+export function FillForm({
+ token,
+ formId,
+ requirement,
+ onComplete,
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+
+ const { forms } = useUiFormsContext();
+ const hook = useAsyncAsHook(() => getContextByFormId(formId, requirement));
+ if (hook === undefined) {
+ return <Loading />;
+ }
+ if (hook.hasError) {
+ return (
+ <Attention
+ title={i18n.str`Could not load context information`}
+ type="danger"
+ >
+ {hook.operational ? hook.details.hint : hook.message}
+ </Attention>
+ );
+ }
+ const formContext = hook.response;
+ const theForm = searchForm(i18n, forms, formId, formContext);
+ const reqId = requirement.id;
+ if (!theForm) {
+ return (
+ <Attention title={i18n.str`Could not find form`} type="danger">
+ <i18n.Translate>
+ Form with id '${formId}' is not registered in this application.
+ </i18n.Translate>
+ </Attention>
+ );
+ }
+ if (!reqId) {
+ return (
+ <Attention title={i18n.str`Can't upload information`} type="danger">
+ <i18n.Translate>
+ The KYC requirement doesn't have an ID
+ </i18n.Translate>
+ </Attention>
+ );
+ }
+
+ return (
+ <ShowForm
+ formId={formId}
+ onComplete={onComplete}
+ reqId={reqId}
+ theForm={theForm}
+ />
+ );
+}
+
function searchForm(
i18n: InternationalizationAPI,
forms: FormMetadata[],
diff --git a/packages/web-util/src/forms/fields/ExternalLink.tsx b/packages/web-util/src/forms/fields/ExternalLink.tsx
@@ -1,7 +1,8 @@
import { TranslatedString } from "@gnu-taler/taler-util";
import { VNode, h } from "preact";
import { RenderAddon } from "./InputLine.js";
-import { Addon } from "../FormProvider.js";
+import { Addon, UIFormProps } from "../FormProvider.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
interface Props {
label: TranslatedString;
@@ -20,8 +21,12 @@ export function ExternalLink({
url,
media,
tooltip,
+ handler,
+ name,
help,
-}: Props): VNode {
+}: Props & UIFormProps<boolean>): VNode {
+ const { value, onChange, error } =
+ handler ?? noHandlerPropsAndNoContextForField(name);
return (
<div class="sm:col-span-6">
{before !== undefined && <RenderAddon addon={before} />}
@@ -30,6 +35,9 @@ export function ExternalLink({
class="underline text-blue-600 hover:text-blue-900 visited:text-purple-600"
target="_blank"
rel="noreferrer"
+ onClick={() => {
+ onChange(true);
+ }}
>
{label}
</a>
diff --git a/packages/web-util/src/forms/fields/InputDownloadLink.tsx b/packages/web-util/src/forms/fields/InputDownloadLink.tsx
@@ -1,8 +1,8 @@
import { TranslatedString } from "@gnu-taler/taler-util";
import { VNode, h } from "preact";
-import { RenderAddon } from "./InputLine.js";
-import { Addon, UIFormProps } from "../FormProvider.js";
import { noHandlerPropsAndNoContextForField } from "../../index.browser.js";
+import { Addon, UIFormProps } from "../FormProvider.js";
+import { RenderAddon } from "./InputLine.js";
interface Props {
label: TranslatedString;
diff --git a/packages/web-util/src/forms/fields/InputToggle.stories.tsx b/packages/web-util/src/forms/fields/InputToggle.stories.tsx
@@ -88,3 +88,23 @@ export const StartUndefinedOnlyTwoStates = tests.createExample(
},
},
);
+
+export const UseTrueValue = tests.createExample(
+ TestedComponent,
+ {
+ initial: {},
+ design: {
+ type: "single-column",
+ fields: [
+ {
+ type: "toggle",
+ label: "do you accept?" as TranslatedString,
+ required: true,
+ id: "accept",
+ trueValue: "YES",
+ onlyTrueValue: true,
+ },
+ ],
+ },
+ },
+);
diff --git a/packages/web-util/src/forms/fields/InputToggle.tsx b/packages/web-util/src/forms/fields/InputToggle.tsx
@@ -10,14 +10,30 @@ import { LabelWithTooltipMaybeRequired } from "./InputLine.js";
* FIXME: Types would be clearer if two/tri state were different types.
*/
export function InputToggle(
- props: { threeState: boolean; defaultValue?: boolean } & UIFormProps<boolean>,
+ props: {
+ threeState: boolean;
+ defaultValue?: boolean;
+ trueValue?: any;
+ falseValue?: any;
+ onlyTrueValue?: boolean;
+ } & UIFormProps<boolean>,
): VNode {
- const { label, tooltip, help, required, threeState, disabled } = props;
+ const {
+ label,
+ tooltip,
+ help,
+ required,
+ threeState,
+ disabled,
+ trueValue = true,
+ falseValue = false,
+ onlyTrueValue = false,
+ } = props;
const { value, onChange, error } =
props.handler ?? noHandlerPropsAndNoContextForField(props.name);
const [dirty, setDirty] = useState<boolean>();
- const isOn = !!value;
+ const isOn = trueValue === value;
if (props.hidden) {
return <Fragment />;
@@ -42,15 +58,26 @@ export function InputToggle(
aria-describedby="availability-description"
onClick={() => {
setDirty(true);
- if (value === false && threeState) {
+ if (value === falseValue && threeState) {
return onChange(undefined as any);
- } else {
- return onChange(!isOn as any);
}
+ if (onlyTrueValue && value === trueValue) {
+ return onChange(undefined as any);
+ }
+ if (value === trueValue) {
+ return onChange(falseValue);
+ }
+ return onChange(trueValue);
}}
>
<span
- data-state={isOn ? "on" : value === undefined && threeState ? "undefined" : "off"}
+ data-state={
+ isOn
+ ? "on"
+ : value === undefined && threeState
+ ? "undefined"
+ : "off"
+ }
class="translate-x-6 data-[state=off]:translate-x-0 data-[state=undefined]:translate-x-3 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
></span>
</button>
diff --git a/packages/web-util/src/forms/forms-types.ts b/packages/web-util/src/forms/forms-types.ts
@@ -18,6 +18,7 @@ import {
buildCodecForObject,
buildCodecForUnion,
Codec,
+ codecForAny,
codecForBoolean,
codecForConstString,
codecForLazy,
@@ -154,7 +155,7 @@ type UIFormElementExternalLink = {
type: "external-link";
url: string;
media?: string;
-} & UIFieldElementDescription;
+} & UIFormFieldBaseConfig;
type UIFormElementHtmlIframe = {
type: "htmlIframe";
@@ -230,6 +231,18 @@ type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
type UIFormFieldToggle = {
type: "toggle";
threeState?: boolean;
+ /**
+ * When the toggle is true use the field is going to have this value.
+ */
+ trueValue?: any;
+ /**
+ * When the toggle is false use the field is going to have this value.
+ */
+ falseValue?: any;
+ /**
+ * Only true value, when the google is false the field is going to be undefined.
+ */
+ onlyTrueValue?: boolean;
} & UIFormFieldBaseConfig;
export type ComputableFieldConfig = {
@@ -396,7 +409,7 @@ const codecForUIFormElementLink = (): Codec<UIFormElementDownloadLink> =>
const codecForUIFormElementExternalLink =
(): Codec<UIFormElementExternalLink> =>
- codecForUIFormFieldBaseDescriptionTemplate<UIFormElementExternalLink>()
+ codecForUIFormFieldBaseConfigTemplate<UIFormElementExternalLink>()
.property("type", codecForConstString("external-link"))
.property("url", codecForString())
.property("media", codecOptional(codecForString()))
@@ -502,6 +515,9 @@ const codecForUiFormFieldTextArea = (): Codec<UIFormFieldTextArea> =>
const codecForUiFormFieldToggle = (): Codec<UIFormFieldToggle> =>
codecForUIFormFieldBaseConfigTemplate<UIFormFieldToggle>()
.property("threeState", codecOptionalDefault(codecForBoolean(), false))
+ .property("falseValue", codecForAny())
+ .property("trueValue", codecForAny())
+ .property("onlyTrueValue", codecOptionalDefault(codecForBoolean(), false))
.property("type", codecForConstString("toggle"))
.build("UIFormFieldToggle");
diff --git a/packages/web-util/src/forms/forms-utils.ts b/packages/web-util/src/forms/forms-utils.ts
@@ -41,18 +41,6 @@ export function convertFormConfigToUiField(
};
return resp;
}
- case "external-link": {
- const resp: UIFormField = {
- type: config.type,
- properties: {
- ...convertBaseFieldsProps(i18n_, config),
- label: i18n_.str`${config.label}`,
- url: config.url,
- media: config.media,
- },
- };
- return resp;
- }
case "htmlIframe": {
const resp: UIFormField = {
type: config.type,
@@ -109,6 +97,23 @@ export function convertFormConfigToUiField(
},
} as UIFormField;
}
+ case "external-link": {
+ return {
+ type: config.type,
+ properties: {
+ ...convertBaseFieldsProps(i18n_, config),
+ ...convertInputFieldsProps(
+ name,
+ handler,
+ config,
+ getConverterByFieldType(config.type, config),
+ ),
+ label: i18n_.str`${config.label}`,
+ url: config.url,
+ media: config.media,
+ },
+ };
+ }
case "download-link": {
return {
type: config.type,
@@ -361,6 +366,9 @@ export function convertFormConfigToUiField(
getConverterByFieldType(config.type, config),
),
threeState: config.threeState,
+ trueValue: config.trueValue,
+ falseValue: config.falseValue,
+ onlyTrueValue: config.onlyTrueValue,
},
} as UIFormField;
}
diff --git a/packages/web-util/src/forms/gana/GLS_Onboarding.ts b/packages/web-util/src/forms/gana/GLS_Onboarding.ts
@@ -61,6 +61,8 @@ export function GLS_Onboarding(
},
{
type: "external-link",
+ id: TalerFormAttributes.DOWNLOADED_TERMS_OF_SERVICE,
+ required: true,
url: "https://google.com",
label: i18n.str`Read the term of service here`,
media: "text/plain",
diff --git a/packages/web-util/src/forms/gana/accept-tos.stories.tsx b/packages/web-util/src/forms/gana/accept-tos.stories.tsx
@@ -31,10 +31,14 @@ export const EmptyForm = tests.createExample(DefaultForm, {
// [TalerFormAttributes.DOWNLOADED_TERMS_OF_SERVICE]: false,
// [TalerFormAttributes.ACCEPTED_TERMS_OF_SERVICE]: false,
},
- design: acceptTos(i18n, {
- tos_url: "https://exchange.demo.taler.net/terms",
- provider_name: "Taler Operations AG",
- }),
+ design: acceptTos(
+ i18n,
+ {
+ tos_url: "https://exchange.demo.taler.net/terms",
+ provider_name: "Taler Operations AG",
+ tosVersion: "v1",
+ },
+ ),
});
export default { title: "accept tos" };
diff --git a/packages/web-util/src/forms/gana/accept-tos.ts b/packages/web-util/src/forms/gana/accept-tos.ts
@@ -35,6 +35,7 @@ export type AcceptTermOfServiceContext = {
provider_name?: string;
expiration_time?: TalerProtocolDuration;
successor_measure?: string;
+ tosVersion: string;
};
// Example context
@@ -56,17 +57,20 @@ export const acceptTos = (
context: AcceptTermOfServiceContext,
): SingleColumnFormDesign => ({
type: "single-column" as const,
- fields: filterUndefined<UIFormElementConfig>([
+ fields: [
{
type: "external-link",
+ id: TalerFormAttributes.DOWNLOADED_TERMS_OF_SERVICE,
+ required: true,
url: context.tos_url,
- label: context.provider_name ?? context.tos_url,
+ //label: context.provider_name ?? context.tos_url,
+ label: i18n.str`View in Browser`,
},
{
type: "download-link",
id: TalerFormAttributes.DOWNLOADED_TERMS_OF_SERVICE,
url: context.tos_url,
- label: "Download PDF version",
+ label: i18n.str`Download PDF version`,
required: true,
media: "application/pdf",
help: i18n.str`You must download to proceed`,
@@ -75,7 +79,9 @@ export const acceptTos = (
type: "toggle",
id: TalerFormAttributes.ACCEPTED_TERMS_OF_SERVICE,
required: true,
- label: i18n.str`Do you accept terms of service?`,
+ trueValue: context.tosVersion,
+ onlyTrueValue: true,
+ label: i18n.str`Do you accept the terms of service?`,
},
- ]),
+ ],
});
diff --git a/packages/web-util/src/index.build.ts b/packages/web-util/src/index.build.ts
@@ -6,10 +6,6 @@ import postcss from "postcss";
import sass from "sass";
import postcssrc from "postcss-load-config";
-// this should give us the current directory where
-// the project is being built
-const BASE = process.cwd();
-
type Assets = {
base: string;
files: string[];
@@ -44,24 +40,9 @@ export function getFilesInDirectory(startPath: string, regex?: RegExp): Assets {
};
}
-let GIT_ROOT = BASE;
-while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
- GIT_ROOT = path.join(GIT_ROOT, "../");
-}
-if (GIT_ROOT === "/") {
- // eslint-disable-next-line no-undef
- console.log("not found");
- // eslint-disable-next-line no-undef
- process.exit(1);
-}
-const GIT_HASH = git_hash();
-
-const buf = fs.readFileSync(path.join(BASE, "package.json"));
-let _package = JSON.parse(buf.toString("utf-8"));
-
-function git_hash() {
+function git_hash(root: string) {
const rev = fs
- .readFileSync(path.join(GIT_ROOT, ".git", "HEAD"))
+ .readFileSync(path.join(root, ".git", "HEAD"))
.toString()
.trim()
.split(/.*[: ]/)
@@ -69,7 +50,10 @@ function git_hash() {
if (rev.indexOf("/") === -1) {
return rev;
} else {
- return fs.readFileSync(path.join(GIT_ROOT, ".git", rev)).toString().trim();
+ return fs
+ .readFileSync(path.join(root, ".git", rev))
+ .toString()
+ .trim();
}
}
@@ -121,50 +105,48 @@ const sassPlugin: esbuild.Plugin = {
},
};
-
/**
- * Problem:
+ * Problem:
* No loader is configured for ".node" files: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
- *
+ *
* Reference:
* https://github.com/evanw/esbuild/issues/1051#issuecomment-806325487
*/
const nativeNodeModulesPlugin: esbuild.Plugin = {
- name: 'native-node-modules',
+ name: "native-node-modules",
setup(build) {
- // If a ".node" file is imported within a module in the "file" namespace, resolve
+ // If a ".node" file is imported within a module in the "file" namespace, resolve
// it to an absolute path and put it into the "node-file" virtual namespace.
- build.onResolve({ filter: /\.node$/, namespace: 'file' }, args => ({
+ build.onResolve({ filter: /\.node$/, namespace: "file" }, (args) => ({
path: require.resolve(args.path, { paths: [args.resolveDir] }),
- namespace: 'node-file',
- }))
+ namespace: "node-file",
+ }));
// Files in the "node-file" virtual namespace call "require()" on the
// path from esbuild of the ".node" file in the output directory.
- build.onLoad({ filter: /.*/, namespace: 'node-file' }, args => ({
+ build.onLoad({ filter: /.*/, namespace: "node-file" }, (args) => ({
contents: `
import path from ${JSON.stringify(args.path)}
try { module.exports = require(path) }
catch {}
`,
- }))
+ }));
// If a ".node" file is imported within a module in the "node-file" namespace, put
// it in the "file" namespace where esbuild's default loading behavior will handle
// it. It is already an absolute path since we resolved it to one above.
- build.onResolve({ filter: /\.node$/, namespace: 'node-file' }, args => ({
+ build.onResolve({ filter: /\.node$/, namespace: "node-file" }, (args) => ({
path: args.path,
- namespace: 'file',
- }))
+ namespace: "file",
+ }));
// Tell esbuild's default loading behavior to use the "file" loader for
// these ".node" files.
- let opts = build.initialOptions
- opts.loader = opts.loader || {}
- opts.loader['.node'] = 'file'
+ let opts = build.initialOptions;
+ opts.loader = opts.loader || {};
+ opts.loader[".node"] = "file";
},
-}
-
+};
const postCssPlugin: esbuild.Plugin = {
name: "custom-build-postcss",
@@ -227,25 +209,54 @@ const defaultEsBuildConfig: esbuild.BuildOptions = {
react: "preact/compat",
"react-dom": "preact/compat",
},
- define: {
- __VERSION__: `"${_package.version}"`,
- __GIT_HASH__: `"${GIT_HASH}"`,
- },
};
-
+import nodePath from "node:path";
+import nodeUrl from "node:url";
export interface BuildParams {
type: "development" | "test" | "production";
+ /**
+ * Assign this to import.meta so the script can be called from any directory
+ */
+ importMeta?: ImportMeta;
source: {
assets: Assets | Assets[];
js: string[];
};
public?: string;
+ /**
+ * Location directory of the output. Can be redirected with INSTALL_DIR environment
+ */
destination: string;
css: "sass" | "postcss" | "linaria";
linariaPlugin?: () => esbuild.Plugin;
}
+function getPackageAndGitRoot(meta: undefined | ImportMeta) {
+ if (meta) {
+ const root = nodePath.dirname(nodeUrl.fileURLToPath(meta.url));
+ process.chdir(root);
+ }
+ const baseDir = process.cwd();
+
+ let GIT_ROOT = baseDir;
+ while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
+ GIT_ROOT = path.join(GIT_ROOT, "../");
+ }
+ if (GIT_ROOT === "/") {
+ throw Error(`could not found git root from ${meta}`);
+ }
+ const hash = git_hash(GIT_ROOT);
+
+ const buf = fs.readFileSync(path.join(baseDir, "package.json"));
+ const pkg = JSON.parse(buf.toString("utf-8"));
+ return { pkg, hash, baseDir };
+}
+
export function computeConfig(params: BuildParams): esbuild.BuildOptions {
+ const { pkg: _package, hash: GIT_HASH, baseDir } = getPackageAndGitRoot(
+ params.importMeta,
+ );
+
const plugins: Array<esbuild.Plugin> = [
copyFilesPlugin(params.source.assets),
];
@@ -289,15 +300,17 @@ export function computeConfig(params: BuildParams): esbuild.BuildOptions {
return {
...defaultEsBuildConfig,
+ absWorkingDir: baseDir,
entryPoints: params.source.js,
publicPath: params.public,
- outdir: params.destination,
+ outdir: process.env.INSTALL_DIR ?? params.destination,
treeShaking: true,
minify: false, //params.type === "production",
sourcemap: true, //params.type !== "production",
define: {
- ...defaultEsBuildConfig.define,
"process.env.NODE_ENV": JSON.stringify(params.type),
+ __VERSION__: `"${_package.version}"`,
+ __GIT_HASH__: `"${GIT_HASH}"`,
},
plugins,
};
@@ -307,17 +320,22 @@ export function computeConfig(params: BuildParams): esbuild.BuildOptions {
* Build sources for prod environment
*/
export async function build(config: BuildParams) {
- const res = await esbuild.build(computeConfig(config));
- fs.writeFileSync(`${config.destination}/version.txt`, `${_package.version}`);
+ const options = computeConfig(config);
+ const res = await esbuild.build(options);
+ fs.writeFileSync(
+ `${config.destination}/version.txt`,
+ options.define ? options.define["__VERSION__"] : "-",
+ );
return res;
}
-const LIVE_RELOAD_SCRIPT = "./node_modules/@gnu-taler/web-util/lib/live-reload.mjs";
+const LIVE_RELOAD_SCRIPT =
+ "./node_modules/@gnu-taler/web-util/lib/live-reload.mjs";
const LIVE_RELOAD_SCRIPT_LOCALLY = "./lib/live-reload.mjs";
/**
* Do startup for development environment
- *
+ *
* To be used from web-utils project
*/
export function initializeDevOnWebUtils(
@@ -331,10 +349,9 @@ export function initializeDevOnWebUtils(
return buildDevelopment;
}
-
/**
* Do startup for development environment
- *
+ *
* To be used when web-utils is a library
*/
export function initializeDev(