mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-13 12:13:42 +01:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ed472502a | ||
|
|
87a30200f9 | ||
|
|
c9ad753672 | ||
|
|
4e7c0095d8 | ||
|
|
07ed9d75fa | ||
|
|
8c0c76e812 | ||
|
|
60ff38cbdc | ||
|
|
405b8b2567 | ||
|
|
489955a913 | ||
|
|
11cbb96746 | ||
|
|
f1ae62b2dc | ||
|
|
4bfb1a106a | ||
|
|
bf47095cc0 | ||
|
|
0489b67f6c | ||
|
|
143acdbd7e | ||
|
|
27698b15bb | ||
|
|
48a9c7e81f | ||
|
|
3c5429897e | ||
|
|
53e766b2a9 |
No files matched your search
Generated
+276
-229
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,152 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import type { Workspace } from "@yaakapp-internal/models";
|
||||
import type { TemplateFunction } from "@yaakapp-internal/plugins";
|
||||
import type { Tokens } from "@yaakapp-internal/templates";
|
||||
import { act, type ReactNode } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeAll, beforeEach, expect, test, vi } from "vite-plus/test";
|
||||
import { TemplateFunctionDialog } from "./TemplateFunctionDialog";
|
||||
|
||||
interface FormProps {
|
||||
data: Record<string, string | boolean>;
|
||||
onChange: (values: Record<string, string | boolean>) => void;
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
parse: vi.fn<(text: string) => Tokens>(),
|
||||
print: vi.fn<(tokens: Tokens) => Promise<string>>(),
|
||||
serialize: vi.fn<(tokens: Tokens) => { data: string }>(),
|
||||
preview: vi.fn<({ template }: { template: string }) => { data: string }>(),
|
||||
form: null as FormProps | null,
|
||||
config: {
|
||||
name: "timestamp.format",
|
||||
args: [
|
||||
{ type: "text", name: "date" },
|
||||
{ type: "text", name: "format" },
|
||||
],
|
||||
} as TemplateFunction,
|
||||
}));
|
||||
|
||||
vi.mock("@yaakapp-internal/templates", () => ({ parseTemplate: mocks.parse }));
|
||||
vi.mock("@yaakapp-internal/ui", () => ({
|
||||
HStack: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
InlineCode: "code",
|
||||
LoadingIcon: () => null,
|
||||
useDebouncedValue: (value: string) => value,
|
||||
}));
|
||||
vi.mock("../hooks/useActiveWorkspace", () => ({ activeWorkspaceAtom: null }));
|
||||
vi.mock("../hooks/useRenderTemplate", () => ({ useRenderTemplate: mocks.preview }));
|
||||
vi.mock("../hooks/useTemplateFunctionConfig", () => ({
|
||||
useTemplateFunctionConfig: () => ({ data: mocks.config }),
|
||||
}));
|
||||
vi.mock("../hooks/useTemplateTokensToString", () => ({
|
||||
templateTokensToString: mocks.print,
|
||||
useTemplateTokensToString: mocks.serialize,
|
||||
}));
|
||||
vi.mock("../lib/dialog", () => ({ showDialog: vi.fn() }));
|
||||
vi.mock("../lib/encryption", () => ({ convertTemplateToInsecure: vi.fn() }));
|
||||
vi.mock("../lib/jotai", () => ({ jotaiStore: { get: vi.fn() } }));
|
||||
vi.mock("../lib/setupOrConfigureEncryption", () => ({ setupOrConfigureEncryption: vi.fn() }));
|
||||
vi.mock("./core/Dialog", () => ({ DialogFooter: () => <button type="submit">Save</button> }));
|
||||
vi.mock("./core/IconButton", () => ({ IconButton: () => <button type="button" /> }));
|
||||
vi.mock("./core/PlainInput", () => ({ PlainInput: () => null }));
|
||||
vi.mock("./DynamicForm", () => ({
|
||||
DYNAMIC_FORM_NULL_ARG: "__NULL__",
|
||||
DynamicForm: (props: FormProps) => {
|
||||
mocks.form = props;
|
||||
return <input aria-label="Timestamp" readOnly value={String(props.data.date ?? "")} />;
|
||||
},
|
||||
}));
|
||||
|
||||
beforeAll(async () => {
|
||||
const base = pathToFileURL(
|
||||
resolve(import.meta.dirname, "../../../crates/yaak-templates/pkg") + "/",
|
||||
);
|
||||
const bindings = await import(new URL("yaak_templates_bg.js", base).href);
|
||||
const { instance } = await WebAssembly.instantiate(
|
||||
await readFile(new URL("yaak_templates_bg.wasm", base)),
|
||||
{ "./yaak_templates_bg.js": bindings },
|
||||
);
|
||||
bindings.__wbg_set_wasm(instance.exports);
|
||||
(instance.exports.__wbindgen_start as () => void)();
|
||||
mocks.parse.mockImplementation(bindings.parse_template);
|
||||
});
|
||||
|
||||
let root: Root;
|
||||
let host: HTMLDivElement;
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
||||
mocks.form = null;
|
||||
// The transport returns an opaque string. Carry the AST as JSON so the assertions can
|
||||
// inspect what Preview and Save receive, without duplicating the backend's printer.
|
||||
mocks.serialize.mockImplementation((tokens) => ({ data: JSON.stringify(tokens) }));
|
||||
mocks.preview.mockImplementation(({ template }) => ({ data: template }));
|
||||
host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
root = createRoot(host);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await act(async () => root.unmount());
|
||||
host.remove();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function functionValue(tokens: Tokens) {
|
||||
const first = tokens.tokens[0];
|
||||
if (first?.type !== "tag" || first.val.type !== "fn") throw new Error("Expected function");
|
||||
return first.val;
|
||||
}
|
||||
|
||||
test.each([
|
||||
["timestamp.offset(expression='-1h')", "${[ timestamp.offset(expression='-1h') ]}"],
|
||||
["date_var", "${[ date_var ]}"],
|
||||
["dates.previous", "${[ dates.previous ]}"],
|
||||
["'${[ date_var ]}'", "${[ date_var ]}"],
|
||||
])("Preview and Save preserve %s until that input changes", async (expression, display) => {
|
||||
const initialTokens = mocks.parse(`\${[ timestamp.format(date=${expression}, format='HH:mm') ]}`);
|
||||
const originalDate = functionValue(initialTokens).args[0];
|
||||
const saved = vi.fn<(value: string) => void>();
|
||||
mocks.print.mockResolvedValue(display);
|
||||
|
||||
await act(async () =>
|
||||
root.render(
|
||||
<TemplateFunctionDialog
|
||||
templateFunction={mocks.config}
|
||||
initialTokens={initialTokens}
|
||||
model={{ id: "wk_test" } as Workspace}
|
||||
onChange={saved}
|
||||
hide={() => {}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(host.querySelector<HTMLInputElement>("input")?.value).toBe(display);
|
||||
|
||||
async function submit() {
|
||||
const form = host.querySelector("form");
|
||||
if (form == null) throw new Error("Dialog did not initialize");
|
||||
await act(async () => {
|
||||
form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
|
||||
});
|
||||
const value = saved.mock.calls.at(-1)?.[0];
|
||||
expect(value).toBe(mocks.preview.mock.calls.at(-1)?.[0].template);
|
||||
return functionValue(JSON.parse(value ?? "null") as Tokens).args;
|
||||
}
|
||||
|
||||
expect((await submit())[0]).toEqual(originalDate);
|
||||
await act(async () => mocks.form?.onChange({ ...mocks.form.data, format: "yyyy" }));
|
||||
expect(await submit()).toEqual([
|
||||
originalDate,
|
||||
{ name: "format", value: { type: "str", text: "yyyy" } },
|
||||
]);
|
||||
|
||||
const edited = "${[ timestamp.offset(expression='-2h') ]}";
|
||||
await act(async () => mocks.form?.onChange({ ...mocks.form.data, date: edited }));
|
||||
expect((await submit())[0]).toEqual({ name: "date", value: { type: "str", text: edited } });
|
||||
await act(async () => mocks.form?.onChange({ ...mocks.form.data, date: "" }));
|
||||
expect((await submit())[0]).toEqual({ name: "date", value: { type: "str", text: "" } });
|
||||
});
|
||||
@@ -24,8 +24,11 @@ import { showDialog } from "../lib/dialog";
|
||||
import { convertTemplateToInsecure } from "../lib/encryption";
|
||||
import { jotaiStore } from "../lib/jotai";
|
||||
import { setupOrConfigureEncryption } from "../lib/setupOrConfigureEncryption";
|
||||
import {
|
||||
collectEditableArgumentValues,
|
||||
preserveUnchangedArgumentExpressions,
|
||||
} from "../lib/templateFunctionArguments";
|
||||
import { DialogFooter } from "./core/Dialog";
|
||||
import { collectArgumentValues } from "./core/Editor/twig/util";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
import { PlainInput } from "./core/PlainInput";
|
||||
import { DYNAMIC_FORM_NULL_ARG, DynamicForm } from "./DynamicForm";
|
||||
@@ -48,7 +51,11 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const initial = collectArgumentValues(initialTokens, templateFunction);
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
templateFunction,
|
||||
templateTokensToString,
|
||||
);
|
||||
|
||||
// HACK: Replace the secure() function's encrypted `value` arg with the decrypted version so
|
||||
// we can display it in the editor input.
|
||||
@@ -74,6 +81,7 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
<InitializedTemplateFunctionDialog
|
||||
{...props}
|
||||
templateFunction={templateFunction}
|
||||
initialTokens={initialTokens}
|
||||
initialArgValues={initialArgValues}
|
||||
/>
|
||||
);
|
||||
@@ -82,10 +90,11 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
function InitializedTemplateFunctionDialog({
|
||||
templateFunction: { name, previewType: ogPreviewType },
|
||||
initialArgValues,
|
||||
initialTokens,
|
||||
hide,
|
||||
onChange,
|
||||
model,
|
||||
}: Omit<Props, "initialTokens"> & {
|
||||
}: Props & {
|
||||
initialArgValues: Record<string, string | boolean>;
|
||||
}) {
|
||||
const previewType = ogPreviewType == null ? "live" : ogPreviewType;
|
||||
@@ -111,12 +120,17 @@ function InitializedTemplateFunctionDialog({
|
||||
val: {
|
||||
type: "fn",
|
||||
name,
|
||||
args: argTokens,
|
||||
args: preserveUnchangedArgumentExpressions(
|
||||
argTokens,
|
||||
initialTokens,
|
||||
initialArgValues,
|
||||
argValues,
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [argValues, name]);
|
||||
}, [argValues, initialArgValues, initialTokens, name]);
|
||||
|
||||
const tagText = useTemplateTokensToString(tokens);
|
||||
const templateFunction = useTemplateFunctionConfig(name, argValues, model);
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import type { TemplateFunction } from "@yaakapp-internal/plugins";
|
||||
import type { FnArg, Tokens } from "@yaakapp-internal/templates";
|
||||
import { beforeAll, describe, expect, test, vi } from "vite-plus/test";
|
||||
import {
|
||||
collectEditableArgumentValues,
|
||||
preserveUnchangedArgumentExpressions,
|
||||
} from "./templateFunctionArguments";
|
||||
|
||||
// Use the shipped WASM parser without the browser-only `?init` loader.
|
||||
let parse: (text: string) => Tokens;
|
||||
beforeAll(async () => {
|
||||
const base = new URL("../../../crates/yaak-templates/pkg/", import.meta.url);
|
||||
const bindings = await import(new URL("yaak_templates_bg.js", base).href);
|
||||
const { instance } = await WebAssembly.instantiate(
|
||||
await readFile(new URL("yaak_templates_bg.wasm", base)),
|
||||
{ "./yaak_templates_bg.js": bindings },
|
||||
);
|
||||
bindings.__wbg_set_wasm(instance.exports);
|
||||
(instance.exports.__wbindgen_start as () => void)();
|
||||
parse = bindings.parse_template;
|
||||
});
|
||||
|
||||
const definition: TemplateFunction = {
|
||||
name: "timestamp.iso8601",
|
||||
args: [{ type: "text", name: "date", optional: true }],
|
||||
};
|
||||
|
||||
function args(tokens: Tokens): FnArg[] {
|
||||
const first = tokens.tokens[0];
|
||||
if (first?.type !== "tag" || first.val.type !== "fn") throw new Error("Expected a function");
|
||||
return first.val.args;
|
||||
}
|
||||
|
||||
function stringArg(name: string, text: string): FnArg {
|
||||
return { name, value: { type: "str", text } };
|
||||
}
|
||||
|
||||
// Printing is an existing async RPC. Assert the exact expression handed to it and return
|
||||
// its expected text, including real parser round-tripping of quoted/escaped fixtures.
|
||||
function printer(text: string) {
|
||||
return vi.fn(async (tokens: Tokens) => {
|
||||
expect(tokens.tokens).toEqual(parse(text).tokens.filter((t) => t.type !== "eof"));
|
||||
return text;
|
||||
});
|
||||
}
|
||||
|
||||
describe("editable template function arguments", () => {
|
||||
test.each([
|
||||
[
|
||||
"reported call",
|
||||
"${[timestamp.iso8601(date=timestamp.offset(expression='-1h')) ]}",
|
||||
"${[ timestamp.offset(expression='-1h') ]}",
|
||||
],
|
||||
["variable", "${[ timestamp.iso8601(date=date_var) ]}", "${[ date_var ]}"],
|
||||
["dotted variable", "${[ timestamp.iso8601(date=dates.previous) ]}", "${[ dates.previous ]}"],
|
||||
[
|
||||
"nested escaping",
|
||||
String.raw`${"${["} timestamp.iso8601(date=outer(value=inner(text='it\'s \\ nested'))) ]}`,
|
||||
String.raw`${"${["} outer(value=inner(text='it\'s \\ nested')) ]}`,
|
||||
],
|
||||
])("loads and preserves %s on unchanged Save", async (_, source, display) => {
|
||||
const initialTokens = parse(source);
|
||||
const snapshot = structuredClone(initialTokens);
|
||||
const print = printer(display);
|
||||
const initial = await collectEditableArgumentValues(initialTokens, definition, print);
|
||||
expect(initial).toEqual({ date: display });
|
||||
expect(print).toHaveBeenCalledOnce();
|
||||
|
||||
const saved = preserveUnchangedArgumentExpressions(
|
||||
[stringArg("date", display)],
|
||||
initialTokens,
|
||||
initial,
|
||||
{ ...initial },
|
||||
);
|
||||
expect(saved).toEqual(args(initialTokens));
|
||||
expect(initialTokens).toEqual(snapshot);
|
||||
});
|
||||
|
||||
test.each([
|
||||
String.raw`${"${["} timestamp.iso8601(date='${"${["} timestamp.offset(expression=\'-1h\') ]}') ]}`,
|
||||
"${[ timestamp.iso8601(date='${[ date_var ]}') ]}",
|
||||
"${[ timestamp.iso8601(date='${[ dates.previous ]}') ]}",
|
||||
"${[ timestamp.iso8601(date='2026-10-27T11:00:00.000Z') ]}",
|
||||
])("leaves existing quoted arguments unchanged: %s", async (source) => {
|
||||
const initialTokens = parse(source);
|
||||
const print = vi.fn<(_: Tokens) => Promise<string>>();
|
||||
const initial = await collectEditableArgumentValues(initialTokens, definition, print);
|
||||
const value = args(initialTokens)[0]?.value;
|
||||
if (value?.type !== "str") throw new Error("Expected a string fixture");
|
||||
expect(initial).toEqual({ date: value.text });
|
||||
expect(print).not.toHaveBeenCalled();
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions(
|
||||
[stringArg("date", value.text)],
|
||||
initialTokens,
|
||||
initial,
|
||||
initial,
|
||||
),
|
||||
).toEqual(args(initialTokens));
|
||||
});
|
||||
|
||||
test("editing a sibling preserves the original expression", async () => {
|
||||
const initialTokens = parse(
|
||||
"${[ timestamp.format(date=timestamp.offset(expression='-1h'), format='HH:mm') ]}",
|
||||
);
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
{
|
||||
name: "timestamp.format",
|
||||
args: [...definition.args, { type: "text", name: "format" }],
|
||||
},
|
||||
printer("${[ timestamp.offset(expression='-1h') ]}"),
|
||||
);
|
||||
const values: Record<string, string | boolean> = { ...initial, format: "yyyy-MM-dd" };
|
||||
const saved = preserveUnchangedArgumentExpressions(
|
||||
[stringArg("date", String(values.date)), stringArg("format", String(values.format))],
|
||||
initialTokens,
|
||||
initial,
|
||||
values,
|
||||
);
|
||||
expect(saved).toEqual([args(initialTokens)[0], stringArg("format", "yyyy-MM-dd")]);
|
||||
});
|
||||
|
||||
test.each(["${[ timestamp.offset(expression='-2h') ]}", "2026-10-26", ""])(
|
||||
"intentional edits use the form's quoted-string convention: %s",
|
||||
async (edited) => {
|
||||
const initialTokens = parse("${[ timestamp.iso8601(date=date_var) ]}");
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
definition,
|
||||
printer("${[ date_var ]}"),
|
||||
);
|
||||
const changed = [stringArg("date", edited)];
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions(changed, initialTokens, initial, { date: edited }),
|
||||
).toEqual(changed);
|
||||
// Returning the input to its original text restores the original expression type.
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions(
|
||||
[stringArg("date", String(initial.date))],
|
||||
initialTokens,
|
||||
initial,
|
||||
{ ...initial },
|
||||
),
|
||||
).toEqual(args(initialTokens));
|
||||
},
|
||||
);
|
||||
|
||||
test("does not resurrect removed or explicitly cleared expression arguments", async () => {
|
||||
const initialTokens = parse("${[ timestamp.iso8601(date=date_var) ]}");
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
definition,
|
||||
printer("${[ date_var ]}"),
|
||||
);
|
||||
expect(preserveUnchangedArgumentExpressions([], initialTokens, initial, {})).toEqual([]);
|
||||
const nullArg: FnArg = { name: "date", value: { type: "null" } };
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions([nullArg], initialTokens, initial, { date: "__NULL__" }),
|
||||
).toEqual([nullArg]);
|
||||
// DynamicForm clears some inputs by leaving a key with undefined; the dialog turns it into ''.
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions([stringArg("date", "")], initialTokens, initial, {}),
|
||||
).toEqual([stringArg("date", "")]);
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions([stringArg("date", "")], initialTokens, {}, {}),
|
||||
).toEqual([stringArg("date", "")]);
|
||||
});
|
||||
|
||||
test("keeps absence, defaults, explicit empty strings, and false distinct", async () => {
|
||||
const fn: TemplateFunction = {
|
||||
name: "test",
|
||||
args: [
|
||||
{ type: "text", name: "absent" },
|
||||
{ type: "text", name: "defaulted", defaultValue: "fallback" },
|
||||
{ type: "text", name: "empty", defaultValue: "fallback" },
|
||||
{ type: "checkbox", name: "flag", defaultValue: "true" },
|
||||
{ type: "checkbox", name: "defaultFlag", defaultValue: "false" },
|
||||
],
|
||||
};
|
||||
const initialTokens = parse("${[ test(empty='', flag=false) ]}");
|
||||
const print = vi.fn<(_: Tokens) => Promise<string>>();
|
||||
const initial = await collectEditableArgumentValues(initialTokens, fn, print);
|
||||
expect(initial).toEqual({
|
||||
defaulted: "fallback",
|
||||
empty: "",
|
||||
flag: false,
|
||||
defaultFlag: "false",
|
||||
});
|
||||
expect(print).not.toHaveBeenCalled();
|
||||
const formArgs: FnArg[] = [
|
||||
stringArg("defaulted", "fallback"),
|
||||
stringArg("empty", ""),
|
||||
{ name: "flag", value: { type: "bool", value: false } },
|
||||
stringArg("defaultFlag", "false"),
|
||||
];
|
||||
expect(preserveUnchangedArgumentExpressions(formArgs, initialTokens, initial, initial)).toEqual(
|
||||
formArgs,
|
||||
);
|
||||
});
|
||||
|
||||
test("an expression overrides defaults, including inputs in a group", async () => {
|
||||
const initialTokens = parse("${[ test(date=dates.previous) ]}");
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
{
|
||||
name: "test",
|
||||
args: [
|
||||
{ type: "h_stack", inputs: [{ type: "text", name: "date", defaultValue: "fallback" }] },
|
||||
],
|
||||
},
|
||||
printer("${[ dates.previous ]}"),
|
||||
);
|
||||
expect(initial).toEqual({ date: "${[ dates.previous ]}" });
|
||||
});
|
||||
|
||||
test("explicit null keeps the existing default and omission behavior", async () => {
|
||||
const initialTokens = parse("${[ test(defaulted=null, omitted=null) ]}");
|
||||
const print = vi.fn<(_: Tokens) => Promise<string>>();
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
{
|
||||
name: "test",
|
||||
args: [
|
||||
{ type: "text", name: "defaulted", defaultValue: "fallback" },
|
||||
{ type: "text", name: "omitted" },
|
||||
],
|
||||
},
|
||||
print,
|
||||
);
|
||||
expect(initial).toEqual({ defaulted: "fallback" });
|
||||
expect(print).not.toHaveBeenCalled();
|
||||
expect(
|
||||
preserveUnchangedArgumentExpressions(
|
||||
[stringArg("defaulted", "fallback")],
|
||||
initialTokens,
|
||||
initial,
|
||||
initial,
|
||||
),
|
||||
).toEqual([stringArg("defaulted", "fallback")]);
|
||||
});
|
||||
|
||||
test("keeps a deeply nested expression's AST without adding string wrappers", async () => {
|
||||
// This shape reaches the native renderer's depth limit. A quoted wrapper around
|
||||
// the date expression would exceed it, even though shallow calls render identically.
|
||||
const expression =
|
||||
"timestamp.iso8601(date=".repeat(44) + "'2026-10-26T10:20:30.000Z'" + ")".repeat(44);
|
||||
const initialTokens = parse(`\${[ timestamp.iso8601(date=${expression}) ]}`);
|
||||
const initial = await collectEditableArgumentValues(
|
||||
initialTokens,
|
||||
definition,
|
||||
printer(`\${[ ${expression} ]}`),
|
||||
);
|
||||
const saved = preserveUnchangedArgumentExpressions(
|
||||
[stringArg("date", String(initial.date))],
|
||||
initialTokens,
|
||||
initial,
|
||||
initial,
|
||||
);
|
||||
expect(saved).toEqual(args(initialTokens));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { TemplateFunction } from "@yaakapp-internal/plugins";
|
||||
import type { FnArg, Tokens } from "@yaakapp-internal/templates";
|
||||
import { collectArgumentValues } from "../components/core/Editor/twig/util";
|
||||
|
||||
type ArgumentValues = Record<string, string | boolean>;
|
||||
|
||||
function functionArgs(tokens: Tokens): FnArg[] {
|
||||
const first = tokens.tokens[0];
|
||||
return first?.type === "tag" && first.val.type === "fn" ? first.val.args : [];
|
||||
}
|
||||
|
||||
/** Adapt expressions to the template text that the form's editors understand. */
|
||||
export async function collectEditableArgumentValues(
|
||||
tokens: Tokens,
|
||||
templateFunction: TemplateFunction,
|
||||
tokensToString: (tokens: Tokens) => Promise<string>,
|
||||
): Promise<ArgumentValues> {
|
||||
const first = tokens.tokens[0];
|
||||
if (first?.type !== "tag" || first.val.type !== "fn") {
|
||||
return collectArgumentValues(tokens, templateFunction);
|
||||
}
|
||||
|
||||
const args = await Promise.all(
|
||||
first.val.args.map(async (arg): Promise<FnArg> => {
|
||||
if (arg.value.type !== "fn" && arg.value.type !== "var") return arg;
|
||||
const text = await tokensToString({ tokens: [{ type: "tag", val: arg.value }] });
|
||||
return { ...arg, value: { type: "str", text } };
|
||||
}),
|
||||
);
|
||||
|
||||
return collectArgumentValues(
|
||||
{ tokens: [{ ...first, val: { ...first.val, args } }] },
|
||||
templateFunction,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displaying an expression as template text must not change its stored type: a string
|
||||
* wrapper adds rendering depth. Only deliberately changed inputs use the form's new values.
|
||||
*/
|
||||
export function preserveUnchangedArgumentExpressions(
|
||||
args: FnArg[],
|
||||
initialTokens: Tokens,
|
||||
initialValues: ArgumentValues,
|
||||
values: ArgumentValues,
|
||||
): FnArg[] {
|
||||
const initialArgs = functionArgs(initialTokens);
|
||||
return args.map((arg) => {
|
||||
const initial = initialArgs.find((a) => a.name === arg.name);
|
||||
if (
|
||||
(initial?.value.type === "fn" || initial?.value.type === "var") &&
|
||||
Object.hasOwn(initialValues, arg.name) &&
|
||||
values[arg.name] === initialValues[arg.name]
|
||||
) {
|
||||
return initial;
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.13.1",
|
||||
"@codemirror/merge": "^6.11.2",
|
||||
"@codemirror/merge": "^6.12.3",
|
||||
"@codemirror/search": "^6.5.11",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@gilbarbara/deep-equal": "^0.4.1",
|
||||
@@ -27,17 +27,17 @@
|
||||
"@replit/codemirror-vim": "^6.3.0",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@shopify/lang-jsonc": "^1.0.1",
|
||||
"@tanstack/react-query": "^5.104.2",
|
||||
"@tanstack/react-router": "^1.170.42",
|
||||
"@tanstack/react-virtual": "^3.14.17",
|
||||
"@tauri-apps/api": "^2.12.3",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.4.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.2",
|
||||
"@tauri-apps/plugin-fs": "^2.7.0",
|
||||
"@tanstack/react-query": "^5.105.0",
|
||||
"@tanstack/react-router": "^1.170.46",
|
||||
"@tanstack/react-virtual": "^3.14.24",
|
||||
"@tauri-apps/api": "^2.12.4",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.5.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.4",
|
||||
"@tauri-apps/plugin-fs": "^2.7.1",
|
||||
"@tauri-apps/plugin-log": "^2.10.1",
|
||||
"@tauri-apps/plugin-opener": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.7.2",
|
||||
"@tauri-apps/plugin-os": "^2.4.0",
|
||||
"@tauri-apps/plugin-shell": "^2.5.0",
|
||||
"@tauri-apps/plugin-shell": "^2.6.0",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.5.1",
|
||||
"cm6-graphql": "^0.2.1",
|
||||
@@ -54,15 +54,15 @@
|
||||
"jotai-family": "^1.0.1",
|
||||
"js-md5": "^0.9.2",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"lucide-react": "^1.55.0",
|
||||
"mime": "^4.1.1",
|
||||
"motion": "^14.0.0",
|
||||
"lucide-react": "^1.64.0",
|
||||
"mime": "^4.1.2",
|
||||
"motion": "^14.8.0",
|
||||
"nanoid": "^6.0.2",
|
||||
"papaparse": "^5.4.1",
|
||||
"parse-color": "^1.0.0",
|
||||
"react": "^19.2.0",
|
||||
"react": "^19.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-dom": "^19.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-pdf": "^11.0.0",
|
||||
"react-syntax-highlighter": "^16.1.2",
|
||||
@@ -78,8 +78,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.8.1",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tanstack/router-plugin": "^1.168.42",
|
||||
"@rolldown/plugin-babel": "^0.3.0",
|
||||
"@tanstack/router-plugin": "^1.168.48",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^22.20.5",
|
||||
"@types/papaparse": "^5.3.16",
|
||||
@@ -93,10 +93,11 @@
|
||||
"@yaakapp-internal/theme": "^1.0.0",
|
||||
"@yaakapp-internal/ui": "^1.0.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"rollup": "^4.64.3",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^0.3.3",
|
||||
"happy-dom": "^21.0.4",
|
||||
"rollup": "^4.66.0",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^1.4.0",
|
||||
"vite-plugin-static-copy": "^4.1.1",
|
||||
"vite-plugin-wasm": "^3.5.0",
|
||||
"vite-plus": "^0.3.3"
|
||||
"vite-plus": "^1.4.0"
|
||||
}
|
||||
}
|
||||
@@ -9,31 +9,31 @@
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.104.2",
|
||||
"@tanstack/react-virtual": "^3.14.17",
|
||||
"@tauri-apps/api": "^2.12.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.2",
|
||||
"@tauri-apps/plugin-opener": "^2.7.1",
|
||||
"@tanstack/react-query": "^5.105.0",
|
||||
"@tanstack/react-virtual": "^3.14.24",
|
||||
"@tauri-apps/api": "^2.12.4",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.4",
|
||||
"@tauri-apps/plugin-opener": "^2.7.2",
|
||||
"@tauri-apps/plugin-os": "^2.4.0",
|
||||
"@yaakapp-internal/proxy-lib": "^1.0.0",
|
||||
"@yaakapp-internal/theme": "^1.0.0",
|
||||
"@yaakapp-internal/ui": "^1.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"jotai": "^2.18.0",
|
||||
"motion": "^14.0.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"motion": "^14.8.0",
|
||||
"react": "^19.3.1",
|
||||
"react-dom": "^19.3.1",
|
||||
"vkbeautify": "^0.99.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@rolldown/plugin-babel": "^0.3.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^6.1.2",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^0.3.3",
|
||||
"vite-plus": "^0.3.3"
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^1.4.0",
|
||||
"vite-plus": "^1.4.0"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ hex = { workspace = true }
|
||||
include_dir = "0.7"
|
||||
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||
log = { workspace = true }
|
||||
rand = "0.8"
|
||||
rand = "0.10"
|
||||
reqwest = { workspace = true, features = ["form"] }
|
||||
rolldown = "0.1.0"
|
||||
# Pinned exactly: rolldown_resolver takes `oxc_resolver = "^11"`, so cargo
|
||||
@@ -42,7 +42,7 @@ tokio = { workspace = true, features = [
|
||||
] }
|
||||
walkdir = "2"
|
||||
webbrowser = "1"
|
||||
zip = "7"
|
||||
zip = "9.0.5"
|
||||
yaak = { workspace = true }
|
||||
yaak-api = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::ui;
|
||||
use crate::utils::http;
|
||||
use base64::Engine as _;
|
||||
use keyring::Entry;
|
||||
use rand::RngCore;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::TryRng;
|
||||
use rand::rngs::SysRng;
|
||||
use reqwest::Url;
|
||||
use serde_json::Value;
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -443,7 +443,7 @@ fn delete_auth_token(environment: Environment) -> CommandResult {
|
||||
|
||||
fn random_hex(bytes: usize) -> String {
|
||||
let mut data = vec![0_u8; bytes];
|
||||
OsRng.fill_bytes(&mut data);
|
||||
SysRng.try_fill_bytes(&mut data).expect("OS random source failed");
|
||||
hex::encode(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::cli::McpArgs;
|
||||
use crate::context::{CliContext, CliExecutionContext};
|
||||
use crate::plugin_events::SendContexts;
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -76,7 +76,7 @@ async fn serve_http(host: CliMcpHost, port: u16, token: Option<String>) -> Resul
|
||||
}
|
||||
|
||||
fn generate_token() -> String {
|
||||
hex::encode(rand::thread_rng().r#gen::<[u8; 32]>())
|
||||
hex::encode(rand::rng().random::<[u8; 32]>())
|
||||
}
|
||||
|
||||
/// Every workspace in the database is "open", and the global `--environment`
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::context::CliContext;
|
||||
use crate::ui;
|
||||
use crate::utils::http;
|
||||
use keyring::Entry;
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
use rolldown::{
|
||||
BundleEvent, Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat,
|
||||
Platform, WatchOption, Watcher, WatcherEvent,
|
||||
@@ -724,9 +724,9 @@ fn random_name() -> String {
|
||||
"yak", "yarn", "year", "yell", "yoke", "yoga", "yam", "yacht", "yodel",
|
||||
];
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let adjective = ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())];
|
||||
let noun = NOUNS[rng.gen_range(0..NOUNS.len())];
|
||||
let mut rng = rand::rng();
|
||||
let adjective = ADJECTIVES[rng.random_range(0..ADJECTIVES.len())];
|
||||
let noun = NOUNS[rng.random_range(0..NOUNS.len())];
|
||||
format!("{adjective}-{noun}")
|
||||
}
|
||||
|
||||
@@ -850,7 +850,7 @@ mod tests {
|
||||
let mut names = HashSet::new();
|
||||
for i in 0..zip.len() {
|
||||
let file = zip.by_index(i).expect("zip entry");
|
||||
names.insert(file.name().to_string());
|
||||
names.insert(file.name().expect("zip entry name").to_string());
|
||||
}
|
||||
|
||||
assert!(names.contains("README.md"));
|
||||
|
||||
@@ -41,7 +41,7 @@ pretty_graphql = "0.2"
|
||||
r2d2 = "0.8.10"
|
||||
r2d2_sqlite = "0.32"
|
||||
mime_guess = "2.0.5"
|
||||
rand = "0.9.0"
|
||||
rand = "0.10.2"
|
||||
reqwest = { workspace = true, features = [
|
||||
"multipart",
|
||||
"gzip",
|
||||
|
||||
@@ -15,7 +15,7 @@ tauri-plugin = { workspace = true, features = ["build"] }
|
||||
cocoa = "0.27.0"
|
||||
log = { workspace = true }
|
||||
objc = "0.2.7"
|
||||
rand = "0.9.0"
|
||||
rand = "0.10.2"
|
||||
csscolorparser = "0.9.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -147,7 +147,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
|
||||
use cocoa::delegate;
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use objc::runtime::{Object, Sel};
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
use rand::distr::Alphanumeric;
|
||||
use std::ffi::c_void;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ publish = false
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
md5 = "0.8.0"
|
||||
rand = "0.9.0"
|
||||
rand = "0.10.2"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ publish = false
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
include_dir = "0.7"
|
||||
log = { workspace = true }
|
||||
nanoid = "0.4.0"
|
||||
nanoid = "0.5.0"
|
||||
rusqlite = { version = "0.38", features = ["bundled", "chrono"] }
|
||||
sea-query-rusqlite = { version = "0.8.0", features = ["with-chrono"] }
|
||||
sea-query = { version = "1.0", features = ["with-chrono", "attr"] }
|
||||
@@ -24,5 +24,5 @@ r2d2_sqlite = { version = "0.32" }
|
||||
# nanoid pulls getrandom, which needs to be told how to reach the browser's
|
||||
# CSPRNG on wasm32-unknown-unknown. Native targets are unaffected.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
getrandom = { version = "0.4.3", features = ["wasm_js"] }
|
||||
uuid = { version = "1", features = ["js"] }
|
||||
@@ -5,7 +5,7 @@ edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
fancy-regex = "0.17"
|
||||
fancy-regex = "0.19"
|
||||
indexmap = "2"
|
||||
sxd-document = "0.3.2"
|
||||
sxd-xpath = "0.4.2"
|
||||
|
||||
@@ -46,7 +46,7 @@ fn regex(pattern: &str) -> Regex {
|
||||
fn replace(
|
||||
re: &Regex,
|
||||
text: &str,
|
||||
rep: impl FnMut(&Captures<'_>) -> String,
|
||||
rep: impl FnMut(&Captures<'_, str>) -> String,
|
||||
) -> Result<String, String> {
|
||||
re.try_replacen(text, 0, rep)
|
||||
.map(|s| s.into_owned())
|
||||
|
||||
@@ -158,7 +158,7 @@ impl JsRegex {
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_values<'a>(captures: &Captures<'_>, from: usize) -> Vec<JsValue<'a>> {
|
||||
fn capture_values<'a>(captures: &Captures<'_, str>, from: usize) -> Vec<JsValue<'a>> {
|
||||
(from..captures.len())
|
||||
.map(|i| captures.get(i).map_or(JsValue::Undefined, |m| JsValue::string(m.as_str())))
|
||||
.collect()
|
||||
|
||||
@@ -12,7 +12,7 @@ dunce = "1.0.4"
|
||||
hyper-rustls = { version = "0.27.7", default-features = false, features = ["http2"] }
|
||||
hyper-util = { version = "0.1.13", default-features = false, features = ["client-legacy"] }
|
||||
log = { workspace = true }
|
||||
md5 = "0.7.0"
|
||||
md5 = "0.8.0"
|
||||
prost = "0.14.4"
|
||||
prost-reflect = { version = "0.16.5", default-features = false, features = ["serde", "derive"] }
|
||||
prost-types = "0.14.4"
|
||||
|
||||
@@ -10,7 +10,7 @@ yaak-database = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
include_dir = "0.7"
|
||||
log = { workspace = true }
|
||||
nanoid = "0.4.0"
|
||||
nanoid = "0.5.0"
|
||||
rusqlite = { version = "0.38", features = ["bundled", "chrono"] }
|
||||
sea-query = { version = "1.0", features = ["with-chrono", "attr"] }
|
||||
sea-query-rusqlite = { version = "0.8.0", features = ["with-chrono"] }
|
||||
|
||||
@@ -12,9 +12,9 @@ futures-util = "0.3.30"
|
||||
hex = { workspace = true }
|
||||
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||
log = { workspace = true }
|
||||
md5 = "0.7.0"
|
||||
md5 = "0.8.0"
|
||||
path-slash = "0.2.1"
|
||||
rand = "0.9.0"
|
||||
rand = "0.10.2"
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -27,4 +27,4 @@ yaak-common = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
zip = "7"
|
||||
zip = "9.0.5"
|
||||
@@ -1,4 +1,4 @@
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
use rand::distr::Alphanumeric;
|
||||
|
||||
pub fn gen_id() -> String {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^11.2.0"
|
||||
"jsonpath-plus": "^11.2.2"
|
||||
},
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -17,6 +17,7 @@ execSync("wasm-pack build --target bundler", {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
CARGO_BUILD_TARGET: "wasm32-unknown-unknown",
|
||||
RUSTFLAGS: `--remap-path-prefix=${cargoHome}=/cargo --remap-path-prefix=${sysroot}=/rustc`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ execSync("wasm-pack build --target bundler", {
|
||||
cwd: __dirname,
|
||||
env: {
|
||||
...process.env,
|
||||
CARGO_BUILD_TARGET: "wasm32-unknown-unknown",
|
||||
CC_wasm32_unknown_unknown: clang,
|
||||
AR_wasm32_unknown_unknown: fs.existsSync(ar)
|
||||
? ar
|
||||
|
||||
Generated
+1291
-1277
File diff suppressed because it is too large.
Load diff
+9
-9
@@ -123,30 +123,30 @@
|
||||
"@codemirror/legacy-modes": "^6.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@rolldown/plugin-babel": "^0.3.0",
|
||||
"@tailwindcss/postcss": "^4.3.2",
|
||||
"@tauri-apps/cli": "npm:@tauri-apps/cli-cef@3.0.0-alpha.6",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@vitejs/plugin-react": "^6.1.2",
|
||||
"@yaakapp/cli": "2026.8.1",
|
||||
"@yaakapp/cli": "2026.9.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"dotenv-cli": "^11.1.0",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.5.29",
|
||||
"postcss": "^8.5.32",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"tar": "^7.5.22",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^0.3.3",
|
||||
"vite-plus": "^0.3.3",
|
||||
"vitest": "^4.1.10",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^1.4.0",
|
||||
"vite-plus": "^1.4.0",
|
||||
"vitest": "^4.1.12",
|
||||
"yauzl": "^3.4.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@vitest/mocker": "^4.1.11",
|
||||
"@vitest/mocker": "^4.1.12",
|
||||
"js-yaml": "^4.3.1",
|
||||
"underscore": "^1.13.8",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^0.3.3"
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^1.4.0"
|
||||
},
|
||||
"packageManager": "npm@11.11.1"
|
||||
}
|
||||
@@ -13,11 +13,11 @@
|
||||
"@yaakapp-internal/rpc-schema": "^1.0.0",
|
||||
"@yaakapp-internal/web": "^1.0.0",
|
||||
"@yaakapp-internal/wasm": "^1.0.0",
|
||||
"@tauri-apps/api": "^2.12.3",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.4.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.2",
|
||||
"@tauri-apps/plugin-fs": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.7.1",
|
||||
"@tauri-apps/api": "^2.12.4",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.5.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.4",
|
||||
"@tauri-apps/plugin-fs": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.7.2",
|
||||
"@tauri-apps/plugin-os": "^2.4.0"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.12.3",
|
||||
"@tauri-apps/api": "^2.12.4",
|
||||
"@yaakapp-internal/plugins": "^1.0.0",
|
||||
"parse-color": "^1.0.0"
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/mcp": "^0.3.2",
|
||||
"@hono/node-server": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "^1.32.1",
|
||||
"hono": "^4.13.13",
|
||||
"zod": "^4.6.6"
|
||||
"@hono/mcp": "^0.3.5",
|
||||
"@hono/node-server": "^2.1.5",
|
||||
"@modelcontextprotocol/sdk": "^1.32.2",
|
||||
"hono": "^4.13.14",
|
||||
"zod": "^4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.20.5",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"test": "vp test --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/filestore": "^0.13.0",
|
||||
"@usebruno/filestore": "^0.14.0",
|
||||
"yaml": "^2.8.3"
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"test": "vp test --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^11.2.0"
|
||||
"jsonpath-plus": "^11.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonpath": "^0.2.4"
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.9.12",
|
||||
"xpath": "^0.0.35"
|
||||
"xpath": "^0.0.36"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user