sv 0.16.6 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +1 -1
- package/dist/{engine-BhR6Vrxy.d.mts → engine-5Sg3lJiQ.d.mts} +1 -1
- package/dist/{engine-DbEL5s1q.mjs → engine-B7chsCPG.mjs} +379 -187
- package/dist/shared.json +96 -0
- package/dist/src/index.d.mts +1 -1
- package/dist/src/index.mjs +1 -1
- package/dist/src/testing.d.mts +1 -1
- package/dist/src/testing.mjs +1 -1
- package/dist/templates/addon/package.json +3 -3
- package/package.json +2 -2
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as name, a as detectPackageManager, d as Command, g as helpConfig, h as forwardExitCode, l as from, n as add, r as create, u as program, v as version } from "./engine-
|
|
2
|
+
import { _ as name, a as detectPackageManager, d as Command, g as helpConfig, h as forwardExitCode, l as from, n as add, r as create, u as program, v as version } from "./engine-B7chsCPG.mjs";
|
|
3
3
|
import { color, resolveCommandArray } from "@sveltejs/sv-utils";
|
|
4
4
|
import V from "node:process";
|
|
5
5
|
import { execSync } from "node:child_process";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { AGENTS, COMMANDS, Walker, coerceVersion, color, constructCommand, createPrinter, dedent, defineEnv, detect, downloadJson, fileExists, isVersionUnsupportedBelow, js, loadFile, loadPackageJson, minVersion, parse, pnpm, resolveCommand, resolveCommandArray, sanitizeName, saveFile, svelte, svelteConfig, transforms } from "@sveltejs/sv-utils";
|
|
2
|
+
import { AGENTS, COMMANDS, KIT3_TSCONFIG, KIT3_TSCONFIG_DEFAULT, Walker, coerceVersion, color, constructCommand, createPrinter, dedent, defineEnv, detect, downloadJson, fileExists, isKit3, isVersionUnsupportedBelow, js, libSubpathImports, loadFile, loadPackageJson, minVersion, parse, pnpm, resolveCommand, resolveCommandArray, resolveLibPrefix, sanitizeName, saveFile, svelte, svelteConfig, transforms } from "@sveltejs/sv-utils";
|
|
3
3
|
import fs, { closeSync, existsSync, openSync, readSync, statSync } from "node:fs";
|
|
4
4
|
import path, { basename, delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
5
5
|
import { promisify, stripVTControlCharacters, styleText } from "node:util";
|
|
@@ -1348,7 +1348,7 @@ ${i ? styleText("cyan", x$1) : ""}
|
|
|
1348
1348
|
//#endregion
|
|
1349
1349
|
//#region package.json
|
|
1350
1350
|
var name = "sv";
|
|
1351
|
-
var version = "0.
|
|
1351
|
+
var version = "0.17.0";
|
|
1352
1352
|
//#endregion
|
|
1353
1353
|
//#region src/core/errors.ts
|
|
1354
1354
|
var UnsupportedError = class extends Error {
|
|
@@ -1361,6 +1361,7 @@ var UnsupportedError = class extends Error {
|
|
|
1361
1361
|
};
|
|
1362
1362
|
//#endregion
|
|
1363
1363
|
//#region src/core/common.ts
|
|
1364
|
+
const RX_IMPORT_ONLY = /^\s*@\S+\s*$/;
|
|
1364
1365
|
const NO_PREFIX = "--no-";
|
|
1365
1366
|
let options$8 = [];
|
|
1366
1367
|
function getLongFlag(flags) {
|
|
@@ -1530,7 +1531,7 @@ function updateAgent(projectPath, language, packageManager, loadedAddons) {
|
|
|
1530
1531
|
const agentFiles = [
|
|
1531
1532
|
"AGENTS.md",
|
|
1532
1533
|
"GEMINI.md",
|
|
1533
|
-
"CLAUDE.md"
|
|
1534
|
+
".claude/CLAUDE.md"
|
|
1534
1535
|
];
|
|
1535
1536
|
const languageLabel = language === "typescript" ? "TypeScript" : language === "checkjs" ? "JavaScript (JSDoc)" : "None";
|
|
1536
1537
|
const packageManagerLabel = packageManager ?? "npm";
|
|
@@ -1549,6 +1550,7 @@ function updateAgent(projectPath, language, packageManager, loadedAddons) {
|
|
|
1549
1550
|
const agentPath = path.join(projectPath, fileName);
|
|
1550
1551
|
if (!fs.existsSync(agentPath)) continue;
|
|
1551
1552
|
let content = fs.readFileSync(agentPath, "utf-8");
|
|
1553
|
+
if (RX_IMPORT_ONLY.test(content)) continue;
|
|
1552
1554
|
content = content.replace(existingSectionPattern, "");
|
|
1553
1555
|
content = configSection + content;
|
|
1554
1556
|
fs.writeFileSync(agentPath, content);
|
|
@@ -5734,6 +5736,263 @@ function createOptionBuilder(options) {
|
|
|
5734
5736
|
};
|
|
5735
5737
|
}
|
|
5736
5738
|
//#endregion
|
|
5739
|
+
//#region src/addons/ai-tools.ts
|
|
5740
|
+
const REGEX_MD = /\.md$/;
|
|
5741
|
+
const CLIENTS = {
|
|
5742
|
+
"claude-code": {
|
|
5743
|
+
label: "Claude Code",
|
|
5744
|
+
pluginSettings: {
|
|
5745
|
+
path: ".claude/settings.json",
|
|
5746
|
+
marketplace: "svelte",
|
|
5747
|
+
repo: "sveltejs/ai-tools",
|
|
5748
|
+
id: "svelte@svelte"
|
|
5749
|
+
},
|
|
5750
|
+
agentPath: "AGENTS.md",
|
|
5751
|
+
agentLink: {
|
|
5752
|
+
path: ".claude/CLAUDE.md",
|
|
5753
|
+
contents: "@../AGENTS.md\n"
|
|
5754
|
+
},
|
|
5755
|
+
configPath: ".mcp.json",
|
|
5756
|
+
skillsPath: ".claude/skills",
|
|
5757
|
+
agentsPath: ".claude/agents",
|
|
5758
|
+
mcpOptions: {
|
|
5759
|
+
typeLocal: "stdio",
|
|
5760
|
+
typeRemote: "http",
|
|
5761
|
+
env: true
|
|
5762
|
+
}
|
|
5763
|
+
},
|
|
5764
|
+
cursor: {
|
|
5765
|
+
label: "Cursor",
|
|
5766
|
+
agentPath: "AGENTS.md",
|
|
5767
|
+
configPath: ".cursor/mcp.json",
|
|
5768
|
+
skillsPath: ".cursor/skills",
|
|
5769
|
+
agentsPath: ".cursor/agents",
|
|
5770
|
+
mcpOptions: {}
|
|
5771
|
+
},
|
|
5772
|
+
gemini: {
|
|
5773
|
+
label: "Gemini",
|
|
5774
|
+
agentPath: "GEMINI.md",
|
|
5775
|
+
configPath: ".gemini/settings.json",
|
|
5776
|
+
skillsPath: ".gemini/skills",
|
|
5777
|
+
agentsPath: ".gemini/agents",
|
|
5778
|
+
schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
|
|
5779
|
+
mcpOptions: {}
|
|
5780
|
+
},
|
|
5781
|
+
opencode: {
|
|
5782
|
+
label: "OpenCode",
|
|
5783
|
+
pluginOnly: true,
|
|
5784
|
+
agentPath: "AGENTS.md",
|
|
5785
|
+
configPath: ".opencode/opencode.json",
|
|
5786
|
+
schema: "https://opencode.ai/config.json",
|
|
5787
|
+
customData: { plugin: ["@sveltejs/opencode"] },
|
|
5788
|
+
extraFiles: [{
|
|
5789
|
+
path: ".opencode/svelte.json",
|
|
5790
|
+
data: { $schema: "https://svelte.dev/opencode/schema.json" }
|
|
5791
|
+
}]
|
|
5792
|
+
},
|
|
5793
|
+
vscode: {
|
|
5794
|
+
label: "VS Code",
|
|
5795
|
+
agentPath: "AGENTS.md",
|
|
5796
|
+
configPath: ".vscode/mcp.json",
|
|
5797
|
+
skillsPath: ".github/skills",
|
|
5798
|
+
agentsPath: ".github/agents",
|
|
5799
|
+
agentExtension: ".agent.md",
|
|
5800
|
+
mcpOptions: { serversKey: "servers" }
|
|
5801
|
+
},
|
|
5802
|
+
other: {
|
|
5803
|
+
label: "Other",
|
|
5804
|
+
agentPath: "AGENTS.md"
|
|
5805
|
+
}
|
|
5806
|
+
};
|
|
5807
|
+
const acceptsTools = (client) => Boolean(client && !client.pluginOnly && (client.skillsPath || client.agentsPath || client.configPath));
|
|
5808
|
+
const wantsLooseTools = (client, delivery) => acceptsTools(client) && (!client.pluginSettings || delivery !== "plugin");
|
|
5809
|
+
const wantsMcpConfig = (client, delivery) => Boolean(client?.mcpOptions) && wantsLooseTools(client, delivery);
|
|
5810
|
+
const TOOLS = {
|
|
5811
|
+
mcp: {
|
|
5812
|
+
label: "MCP server",
|
|
5813
|
+
kind: "mcp"
|
|
5814
|
+
},
|
|
5815
|
+
"svelte-code-writer": {
|
|
5816
|
+
label: "svelte-code-writer",
|
|
5817
|
+
kind: "skill",
|
|
5818
|
+
hint: "skill"
|
|
5819
|
+
},
|
|
5820
|
+
"svelte-core-bestpractices": {
|
|
5821
|
+
label: "svelte-core-bestpractices",
|
|
5822
|
+
kind: "skill",
|
|
5823
|
+
hint: "skill"
|
|
5824
|
+
},
|
|
5825
|
+
"svelte-file-editor": {
|
|
5826
|
+
label: "svelte-file-editor",
|
|
5827
|
+
kind: "agent",
|
|
5828
|
+
hint: "sub-agent"
|
|
5829
|
+
}
|
|
5830
|
+
};
|
|
5831
|
+
var ai_tools_default = defineAddon({
|
|
5832
|
+
id: "ai-tools",
|
|
5833
|
+
shortDescription: "Svelte AI tools",
|
|
5834
|
+
homepage: "https://svelte.dev/docs/ai",
|
|
5835
|
+
options: defineAddonOptions().add("ide", {
|
|
5836
|
+
question: "Which client would you like to use?",
|
|
5837
|
+
type: "multiselect",
|
|
5838
|
+
default: [],
|
|
5839
|
+
options: Object.entries(CLIENTS).map(([value, client]) => ({
|
|
5840
|
+
value,
|
|
5841
|
+
label: client.label
|
|
5842
|
+
})),
|
|
5843
|
+
required: true
|
|
5844
|
+
}).add("delivery", {
|
|
5845
|
+
question: "How would you like to add the Svelte tools?",
|
|
5846
|
+
type: "select",
|
|
5847
|
+
default: "plugin",
|
|
5848
|
+
options: [{
|
|
5849
|
+
value: "plugin",
|
|
5850
|
+
label: "Svelte plugin",
|
|
5851
|
+
hint: "recommended, auto-installs & updates"
|
|
5852
|
+
}, {
|
|
5853
|
+
value: "tools",
|
|
5854
|
+
label: "Individual tools",
|
|
5855
|
+
hint: "choose exactly what to add"
|
|
5856
|
+
}],
|
|
5857
|
+
condition: ({ ide }) => ide.some((i) => CLIENTS[i]?.pluginSettings)
|
|
5858
|
+
}).add("tools", {
|
|
5859
|
+
question: "Which tools would you like to add?",
|
|
5860
|
+
type: "multiselect",
|
|
5861
|
+
default: Object.keys(TOOLS),
|
|
5862
|
+
options: Object.entries(TOOLS).map(([value, t]) => ({
|
|
5863
|
+
value,
|
|
5864
|
+
label: t.label,
|
|
5865
|
+
hint: t.hint
|
|
5866
|
+
})),
|
|
5867
|
+
required: false,
|
|
5868
|
+
condition: ({ ide, delivery }) => ide.some((i) => wantsLooseTools(CLIENTS[i], delivery))
|
|
5869
|
+
}).add("mcpSetup", {
|
|
5870
|
+
question: "Which MCP setup would you like to use?",
|
|
5871
|
+
type: "select",
|
|
5872
|
+
default: "remote",
|
|
5873
|
+
options: [{
|
|
5874
|
+
value: "local",
|
|
5875
|
+
label: "Local",
|
|
5876
|
+
hint: "will use stdio"
|
|
5877
|
+
}, {
|
|
5878
|
+
value: "remote",
|
|
5879
|
+
label: "Remote",
|
|
5880
|
+
hint: "will use a remote endpoint"
|
|
5881
|
+
}],
|
|
5882
|
+
condition: ({ ide, delivery, tools }) => (tools === void 0 || tools.includes("mcp")) && ide.some((i) => wantsMcpConfig(CLIENTS[i], delivery))
|
|
5883
|
+
}).build(),
|
|
5884
|
+
run: ({ sv, options }) => {
|
|
5885
|
+
const usePlugin = options.delivery === "plugin";
|
|
5886
|
+
const selected = options.tools ?? Object.keys(TOOLS);
|
|
5887
|
+
const selectedSkills = selected.filter((t) => TOOLS[t]?.kind === "skill");
|
|
5888
|
+
const selectedAgents = selected.filter((t) => TOOLS[t]?.kind === "agent");
|
|
5889
|
+
const wantsMcp = selected.includes("mcp");
|
|
5890
|
+
const getLocalConfig = (o) => {
|
|
5891
|
+
return {
|
|
5892
|
+
...o?.typeLocal ? { type: o.typeLocal } : {},
|
|
5893
|
+
command: o?.command ?? "npx",
|
|
5894
|
+
...o?.env ? { env: {} } : {},
|
|
5895
|
+
...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
|
|
5896
|
+
};
|
|
5897
|
+
};
|
|
5898
|
+
const getRemoteConfig = (o) => {
|
|
5899
|
+
return {
|
|
5900
|
+
...o?.typeRemote ? { type: o.typeRemote } : {},
|
|
5901
|
+
url: "https://mcp.svelte.dev/mcp"
|
|
5902
|
+
};
|
|
5903
|
+
};
|
|
5904
|
+
const filesAdded = [];
|
|
5905
|
+
const filesExistingAlready = [];
|
|
5906
|
+
const sharedFiles = getSharedFiles();
|
|
5907
|
+
const mcpFiles = sharedFiles.filter((file) => file.include.includes("mcp"));
|
|
5908
|
+
const skillFiles = sharedFiles.filter((file) => file.include.includes("skills"));
|
|
5909
|
+
const agentFiles = sharedFiles.filter((file) => file.include.includes("agents"));
|
|
5910
|
+
const agentFile = mcpFiles.find((file) => file.name === "AGENTS.md");
|
|
5911
|
+
const addFile = (path, contents) => {
|
|
5912
|
+
sv.file(path, (content) => {
|
|
5913
|
+
if (content) {
|
|
5914
|
+
filesExistingAlready.push(path);
|
|
5915
|
+
return false;
|
|
5916
|
+
}
|
|
5917
|
+
if (!filesAdded.includes(path)) filesAdded.push(path);
|
|
5918
|
+
return contents;
|
|
5919
|
+
});
|
|
5920
|
+
};
|
|
5921
|
+
for (const ide of options.ide) {
|
|
5922
|
+
const client = CLIENTS[ide];
|
|
5923
|
+
if (!client) continue;
|
|
5924
|
+
if (client.pluginSettings && usePlugin) {
|
|
5925
|
+
const plugin = client.pluginSettings;
|
|
5926
|
+
sv.file(plugin.path, transforms.json(({ data }) => {
|
|
5927
|
+
data.extraKnownMarketplaces ??= {};
|
|
5928
|
+
data.extraKnownMarketplaces[plugin.marketplace] ??= { source: {
|
|
5929
|
+
source: "github",
|
|
5930
|
+
repo: plugin.repo
|
|
5931
|
+
} };
|
|
5932
|
+
data.enabledPlugins ??= {};
|
|
5933
|
+
data.enabledPlugins[plugin.id] = true;
|
|
5934
|
+
}));
|
|
5935
|
+
continue;
|
|
5936
|
+
}
|
|
5937
|
+
if (!client.agentPath) continue;
|
|
5938
|
+
const { mcpOptions, agentPath, agentLink, configPath, skillsPath, agentsPath, agentExtension, schema, customData, extraFiles } = client;
|
|
5939
|
+
if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
|
|
5940
|
+
if (content) {
|
|
5941
|
+
filesExistingAlready.push(agentPath);
|
|
5942
|
+
return false;
|
|
5943
|
+
}
|
|
5944
|
+
filesAdded.push(agentPath);
|
|
5945
|
+
return agentFile?.contents ?? "";
|
|
5946
|
+
});
|
|
5947
|
+
if (agentLink) addFile(agentLink.path, agentLink.contents);
|
|
5948
|
+
const writeMcp = Boolean(mcpOptions) && wantsMcp;
|
|
5949
|
+
if (configPath && (writeMcp || customData)) sv.file(configPath, transforms.json(({ data }) => {
|
|
5950
|
+
if (schema) data["$schema"] = schema;
|
|
5951
|
+
if (customData) for (const [key, value] of Object.entries(customData)) data[key] = value;
|
|
5952
|
+
if (writeMcp) {
|
|
5953
|
+
const key = mcpOptions.serversKey ?? "mcpServers";
|
|
5954
|
+
data[key] ??= {};
|
|
5955
|
+
data[key].svelte = options.mcpSetup === "local" ? getLocalConfig(mcpOptions) : getRemoteConfig(mcpOptions);
|
|
5956
|
+
}
|
|
5957
|
+
}));
|
|
5958
|
+
if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
|
|
5959
|
+
for (const [key, value] of Object.entries(extra.data)) data[key] = value;
|
|
5960
|
+
}));
|
|
5961
|
+
if (skillsPath) for (const file of skillFiles) {
|
|
5962
|
+
if (!selectedSkills.includes(file.name.split("/")[0])) continue;
|
|
5963
|
+
addFile(`${skillsPath}/${file.name}`, file.contents);
|
|
5964
|
+
}
|
|
5965
|
+
if (agentsPath) for (const file of agentFiles) {
|
|
5966
|
+
if (!selectedAgents.includes(file.name.replace(REGEX_MD, ""))) continue;
|
|
5967
|
+
const ext = agentExtension ?? ".md";
|
|
5968
|
+
addFile(`${agentsPath}/${file.name.replace(REGEX_MD, ext)}`, file.contents);
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
if (filesExistingAlready.length > 0) R$1.warn(`${filesExistingAlready.map((path) => color.path(path)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${color.website("https://svelte.dev/docs/ai")} for manual setup.`);
|
|
5972
|
+
},
|
|
5973
|
+
nextSteps({ options, cwd }) {
|
|
5974
|
+
const steps = [];
|
|
5975
|
+
for (const ide of options.ide) {
|
|
5976
|
+
const client = CLIENTS[ide];
|
|
5977
|
+
if (!client?.pluginSettings) continue;
|
|
5978
|
+
const plugin = client.pluginSettings;
|
|
5979
|
+
const settingsPath = path.resolve(cwd, plugin.path);
|
|
5980
|
+
if (!fs.existsSync(settingsPath)) continue;
|
|
5981
|
+
let pluginEnabled = false;
|
|
5982
|
+
try {
|
|
5983
|
+
pluginEnabled = Boolean(JSON.parse(fs.readFileSync(settingsPath, "utf8")).enabledPlugins?.[plugin.id]);
|
|
5984
|
+
} catch {}
|
|
5985
|
+
if (!pluginEnabled) continue;
|
|
5986
|
+
const looseDirs = [client.skillsPath, client.agentsPath].filter((dir) => Boolean(dir) && fs.existsSync(path.resolve(cwd, dir)) && fs.readdirSync(path.resolve(cwd, dir)).some((entry) => Object.keys(TOOLS).includes(entry.replace(REGEX_MD, ""))));
|
|
5987
|
+
if (looseDirs.length === 0) continue;
|
|
5988
|
+
steps.push(`${client.label}: the Svelte plugin is enabled and ${looseDirs.map((d) => color.path(d)).join(" + ")} also exist${looseDirs.length > 1 ? "" : "s"} - remove ${looseDirs.length > 1 ? "them" : "it"} (or disable the plugin) to avoid duplicate skills/sub-agents.`);
|
|
5989
|
+
}
|
|
5990
|
+
if (options.delivery === "plugin" && options.ide.includes("claude-code")) steps.push(`Open the project in Claude Code and trust the workspace - the Svelte plugin installs automatically.`);
|
|
5991
|
+
if (options.ide.includes("other")) steps.push(`For other clients: ${color.website(`https://svelte.dev/docs/ai/${options.mcpSetup ?? "remote"}-setup#Other-clients`)}`);
|
|
5992
|
+
return steps;
|
|
5993
|
+
}
|
|
5994
|
+
});
|
|
5995
|
+
//#endregion
|
|
5737
5996
|
//#region src/addons/common.ts
|
|
5738
5997
|
const ESLINT_VERSION = "^10.4.1";
|
|
5739
5998
|
const addEslintConfigPrettier = transforms.script(({ ast, js }) => {
|
|
@@ -5886,6 +6145,7 @@ var better_auth_default = defineAddon({
|
|
|
5886
6145
|
runsAfter("experimental");
|
|
5887
6146
|
},
|
|
5888
6147
|
run: ({ sv, cwd, language, options, directory, dependencyVersion, file }) => {
|
|
6148
|
+
const lib = resolveLibPrefix(dependencyVersion("@sveltejs/kit"));
|
|
5889
6149
|
const svelteVersion = dependencyVersion("svelte");
|
|
5890
6150
|
const svelte5 = !!svelteVersion && coerceVersion(svelteVersion).major === 5;
|
|
5891
6151
|
const [ts, s5] = createPrinter(language === "ts", svelte5);
|
|
@@ -5932,7 +6192,7 @@ var better_auth_default = defineAddon({
|
|
|
5932
6192
|
}
|
|
5933
6193
|
sv.file(`${directory.lib}/server/auth.${language}`, transforms.script(({ ast, comments, js }) => {
|
|
5934
6194
|
js.imports.addNamed(ast, {
|
|
5935
|
-
from:
|
|
6195
|
+
from: `${lib}/server/db`,
|
|
5936
6196
|
imports: [d1 ? "getDb" : "db"]
|
|
5937
6197
|
});
|
|
5938
6198
|
js.imports.addNamed(ast, {
|
|
@@ -5989,7 +6249,7 @@ var better_auth_default = defineAddon({
|
|
|
5989
6249
|
/**
|
|
5990
6250
|
* DO NOT USE!
|
|
5991
6251
|
*
|
|
5992
|
-
* This instance is used by the \`
|
|
6252
|
+
* This instance is used by the \`auth\` CLI for schema generation ONLY.
|
|
5993
6253
|
* To access \`auth\` at runtime, use \`event.locals.auth\`.
|
|
5994
6254
|
*/
|
|
5995
6255
|
export const auth = createAuth(null${ts("!")});`;
|
|
@@ -6013,7 +6273,7 @@ var better_auth_default = defineAddon({
|
|
|
6013
6273
|
const authConfigPath = `${directory.lib}/server/auth.${language}`;
|
|
6014
6274
|
const authSchemaPath = `${directory.lib}/server/db/auth.schema.${language}`;
|
|
6015
6275
|
sv.file(file.package, transforms.json(({ data, json }) => {
|
|
6016
|
-
json.packageScriptsUpsert(data, "auth:schema", `
|
|
6276
|
+
json.packageScriptsUpsert(data, "auth:schema", `auth generate --config ${authConfigPath} --output ${authSchemaPath} --yes`);
|
|
6017
6277
|
}));
|
|
6018
6278
|
sv.file(`${directory.lib}/server/db/auth.schema.${language}`, transforms.text(({ content }) => {
|
|
6019
6279
|
if (content) return false;
|
|
@@ -6028,7 +6288,7 @@ var better_auth_default = defineAddon({
|
|
|
6028
6288
|
sv.file("src/app.d.ts", transforms.script(({ ast, comments, js }) => {
|
|
6029
6289
|
if (d1) js.imports.addNamed(ast, {
|
|
6030
6290
|
imports: ["createAuth"],
|
|
6031
|
-
from:
|
|
6291
|
+
from: `${lib}/server/auth`
|
|
6032
6292
|
});
|
|
6033
6293
|
js.imports.addNamed(ast, {
|
|
6034
6294
|
imports: ["User", "Session"],
|
|
@@ -6052,7 +6312,7 @@ var better_auth_default = defineAddon({
|
|
|
6052
6312
|
});
|
|
6053
6313
|
js.imports.addNamed(ast, {
|
|
6054
6314
|
imports: [d1 ? "createAuth" : "auth"],
|
|
6055
|
-
from:
|
|
6315
|
+
from: `${lib}/server/auth`
|
|
6056
6316
|
});
|
|
6057
6317
|
env.importEnv(ast, js, ["building"]);
|
|
6058
6318
|
const handleContent = dedent`
|
|
@@ -6160,7 +6420,7 @@ var better_auth_default = defineAddon({
|
|
|
6160
6420
|
import { fail, redirect } from '@sveltejs/kit';
|
|
6161
6421
|
${ts("import type { Actions } from './$types';")}
|
|
6162
6422
|
${ts("import type { PageServerLoad } from './$types';")}
|
|
6163
|
-
${!d1 ?
|
|
6423
|
+
${!d1 ? `import { auth } from '${lib}/server/auth';` : ""}
|
|
6164
6424
|
${needsAPIError ? "import { APIError } from 'better-auth/api';" : ""}
|
|
6165
6425
|
|
|
6166
6426
|
export const load${ts(": PageServerLoad")} = (event) => {
|
|
@@ -6229,7 +6489,7 @@ var better_auth_default = defineAddon({
|
|
|
6229
6489
|
import { redirect } from '@sveltejs/kit';
|
|
6230
6490
|
${ts("import type { Actions } from './$types';")}
|
|
6231
6491
|
${ts("import type { PageServerLoad } from './$types';")}
|
|
6232
|
-
${!d1 ?
|
|
6492
|
+
${!d1 ? `import { auth } from '${lib}/server/auth';` : ""}
|
|
6233
6493
|
|
|
6234
6494
|
export const load${ts(": PageServerLoad")} = (event) => {
|
|
6235
6495
|
if (!event.locals.user) {
|
|
@@ -6396,7 +6656,7 @@ var drizzle_default = defineAddon({
|
|
|
6396
6656
|
runsAfter("experimental");
|
|
6397
6657
|
if (!isKit) return unsupported("Requires SvelteKit");
|
|
6398
6658
|
},
|
|
6399
|
-
run: ({ sv, language, options, directory, dependencyVersion, cwd, cancel, file
|
|
6659
|
+
run: ({ sv, language, options, directory, dependencyVersion, cwd, cancel, file }) => {
|
|
6400
6660
|
const [ts] = createPrinter(language === "ts");
|
|
6401
6661
|
const baseDBPath = path.resolve(cwd, directory.lib, "server", "db");
|
|
6402
6662
|
const paths = {
|
|
@@ -6413,9 +6673,8 @@ var drizzle_default = defineAddon({
|
|
|
6413
6673
|
if (options.postgresql === "neon") sv.devDependency("@neondatabase/serverless", "^1.1.0");
|
|
6414
6674
|
if (options.postgresql === "postgres.js") sv.devDependency("postgres", "^3.4.9");
|
|
6415
6675
|
if (options.sqlite === "better-sqlite3") {
|
|
6416
|
-
sv.dependency("better-sqlite3", "^
|
|
6676
|
+
sv.dependency("better-sqlite3", "^13.0.2");
|
|
6417
6677
|
sv.devDependency("@types/better-sqlite3", "^7.6.13");
|
|
6418
|
-
if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("better-sqlite3"));
|
|
6419
6678
|
}
|
|
6420
6679
|
if (options.sqlite === "libsql" || options.sqlite === "turso") sv.devDependency("@libsql/client", "^0.17.3");
|
|
6421
6680
|
sv.file(".env", generateEnv(options, false));
|
|
@@ -6513,7 +6772,13 @@ var drizzle_default = defineAddon({
|
|
|
6513
6772
|
})
|
|
6514
6773
|
`) });
|
|
6515
6774
|
}));
|
|
6516
|
-
|
|
6775
|
+
if (isKit3(dependencyVersion("@sveltejs/kit"))) {
|
|
6776
|
+
const configFile = language === "ts" ? "tsconfig.json" : "jsconfig.json";
|
|
6777
|
+
if (fileExists(cwd, configFile)) sv.file(configFile, transforms.json(({ data }) => {
|
|
6778
|
+
const include = data.include ??= ["src"];
|
|
6779
|
+
if (!include.includes(`drizzle.config.${language}`)) include.push(`drizzle.config.${language}`);
|
|
6780
|
+
}));
|
|
6781
|
+
} else svelteConfig.edit({
|
|
6517
6782
|
sv,
|
|
6518
6783
|
cwd
|
|
6519
6784
|
}, ({ override, js }) => {
|
|
@@ -6929,12 +7194,25 @@ const FEATURES = {
|
|
|
6929
7194
|
hint: "kit ^2 only",
|
|
6930
7195
|
inNext: false
|
|
6931
7196
|
},
|
|
6932
|
-
handleRenderingErrors: {
|
|
7197
|
+
handleRenderingErrors: {
|
|
7198
|
+
label: "rendering error boundaries",
|
|
7199
|
+
hint: "kit ^2 only",
|
|
7200
|
+
inNext: false
|
|
7201
|
+
},
|
|
6933
7202
|
forkPreloads: {
|
|
6934
7203
|
label: "forked preloading",
|
|
6935
7204
|
off: true
|
|
6936
7205
|
}
|
|
6937
7206
|
};
|
|
7207
|
+
const SOURCE_EXTENSIONS = [
|
|
7208
|
+
".svelte",
|
|
7209
|
+
".svelte.ts",
|
|
7210
|
+
".svelte.js",
|
|
7211
|
+
".ts",
|
|
7212
|
+
".js",
|
|
7213
|
+
".svx",
|
|
7214
|
+
".md"
|
|
7215
|
+
];
|
|
6938
7216
|
const KIT3_PEERS = {
|
|
6939
7217
|
vite: "^8.0.0",
|
|
6940
7218
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
@@ -6966,7 +7244,7 @@ var experimental_default = defineAddon({
|
|
|
6966
7244
|
required: false
|
|
6967
7245
|
}).build(),
|
|
6968
7246
|
setup: ({ runsAfter }) => runsAfter("sveltekitAdapter"),
|
|
6969
|
-
run: ({ sv, cwd, options, language, dependencyVersion }) => {
|
|
7247
|
+
run: ({ sv, cwd, options, language, directory, dependencyVersion }) => {
|
|
6970
7248
|
const kitNext = options.versions.includes("kit");
|
|
6971
7249
|
if (kitNext) {
|
|
6972
7250
|
sv.devDependency("@sveltejs/kit", "next");
|
|
@@ -6982,6 +7260,23 @@ var experimental_default = defineAddon({
|
|
|
6982
7260
|
};
|
|
6983
7261
|
for (const name of Object.keys(deps)) if (name.startsWith("@sveltejs/adapter-")) sv.devDependency(name, "next");
|
|
6984
7262
|
}
|
|
7263
|
+
if (kitNext) {
|
|
7264
|
+
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
7265
|
+
if (!fileExists(cwd, name)) continue;
|
|
7266
|
+
sv.file(name, transforms.json(({ data }) => {
|
|
7267
|
+
data.extends = KIT3_TSCONFIG;
|
|
7268
|
+
data.include ??= [directory.src];
|
|
7269
|
+
for (const [key, value] of Object.entries(data.compilerOptions ?? {})) if (KIT3_TSCONFIG_DEFAULT[key] === value) delete data.compilerOptions[key];
|
|
7270
|
+
}));
|
|
7271
|
+
}
|
|
7272
|
+
sv.file("package.json", transforms.json(({ data }) => {
|
|
7273
|
+
data.imports = {
|
|
7274
|
+
...libSubpathImports(directory.lib),
|
|
7275
|
+
...data.imports
|
|
7276
|
+
};
|
|
7277
|
+
}));
|
|
7278
|
+
for (const relative of sourceFiles(cwd, directory.src)) sv.file(relative, (content) => content.includes("$lib") ? content.replaceAll("$lib", "#lib") : false);
|
|
7279
|
+
}
|
|
6985
7280
|
const config = {};
|
|
6986
7281
|
for (const [name, f] of Object.entries(FEATURES)) {
|
|
6987
7282
|
if (!options.features.includes(name)) continue;
|
|
@@ -6997,147 +7292,12 @@ var experimental_default = defineAddon({
|
|
|
6997
7292
|
}, ({ override }) => override(config));
|
|
6998
7293
|
}
|
|
6999
7294
|
});
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
type: "multiselect",
|
|
7007
|
-
default: [],
|
|
7008
|
-
options: [
|
|
7009
|
-
{
|
|
7010
|
-
value: "claude-code",
|
|
7011
|
-
label: "claude code"
|
|
7012
|
-
},
|
|
7013
|
-
{
|
|
7014
|
-
value: "cursor",
|
|
7015
|
-
label: "Cursor"
|
|
7016
|
-
},
|
|
7017
|
-
{
|
|
7018
|
-
value: "gemini",
|
|
7019
|
-
label: "Gemini"
|
|
7020
|
-
},
|
|
7021
|
-
{
|
|
7022
|
-
value: "opencode",
|
|
7023
|
-
label: "opencode"
|
|
7024
|
-
},
|
|
7025
|
-
{
|
|
7026
|
-
value: "vscode",
|
|
7027
|
-
label: "VSCode"
|
|
7028
|
-
},
|
|
7029
|
-
{
|
|
7030
|
-
value: "other",
|
|
7031
|
-
label: "Other"
|
|
7032
|
-
}
|
|
7033
|
-
],
|
|
7034
|
-
required: true
|
|
7035
|
-
}).add("setup", {
|
|
7036
|
-
question: "What setup would you like to use?",
|
|
7037
|
-
type: "select",
|
|
7038
|
-
default: "remote",
|
|
7039
|
-
options: [{
|
|
7040
|
-
value: "local",
|
|
7041
|
-
label: "Local",
|
|
7042
|
-
hint: "will use stdio"
|
|
7043
|
-
}, {
|
|
7044
|
-
value: "remote",
|
|
7045
|
-
label: "Remote",
|
|
7046
|
-
hint: "will use a remote endpoint"
|
|
7047
|
-
}],
|
|
7048
|
-
required: true,
|
|
7049
|
-
condition: ({ ide }) => !(ide.length === 1 && ide.includes("opencode"))
|
|
7050
|
-
}).build(),
|
|
7051
|
-
run: ({ sv, options }) => {
|
|
7052
|
-
const getLocalConfig = (o) => {
|
|
7053
|
-
return {
|
|
7054
|
-
...o?.typeLocal ? { type: o.typeLocal } : {},
|
|
7055
|
-
command: o?.command ?? "npx",
|
|
7056
|
-
...o?.env ? { env: {} } : {},
|
|
7057
|
-
...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
|
|
7058
|
-
};
|
|
7059
|
-
};
|
|
7060
|
-
const getRemoteConfig = (o) => {
|
|
7061
|
-
return {
|
|
7062
|
-
...o?.typeRemote ? { type: o.typeRemote } : {},
|
|
7063
|
-
url: "https://mcp.svelte.dev/mcp"
|
|
7064
|
-
};
|
|
7065
|
-
};
|
|
7066
|
-
const configurator = {
|
|
7067
|
-
"claude-code": {
|
|
7068
|
-
agentPath: "CLAUDE.md",
|
|
7069
|
-
configPath: ".mcp.json",
|
|
7070
|
-
mcpOptions: {
|
|
7071
|
-
typeLocal: "stdio",
|
|
7072
|
-
typeRemote: "http",
|
|
7073
|
-
env: true
|
|
7074
|
-
}
|
|
7075
|
-
},
|
|
7076
|
-
cursor: {
|
|
7077
|
-
agentPath: "AGENTS.md",
|
|
7078
|
-
configPath: ".cursor/mcp.json",
|
|
7079
|
-
mcpOptions: {}
|
|
7080
|
-
},
|
|
7081
|
-
gemini: {
|
|
7082
|
-
agentPath: "GEMINI.md",
|
|
7083
|
-
configPath: ".gemini/settings.json",
|
|
7084
|
-
schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
|
|
7085
|
-
mcpOptions: {}
|
|
7086
|
-
},
|
|
7087
|
-
opencode: {
|
|
7088
|
-
agentPath: "AGENTS.md",
|
|
7089
|
-
configPath: ".opencode/opencode.json",
|
|
7090
|
-
schema: "https://opencode.ai/config.json",
|
|
7091
|
-
customData: { plugin: ["@sveltejs/opencode"] },
|
|
7092
|
-
extraFiles: [{
|
|
7093
|
-
path: ".opencode/svelte.json",
|
|
7094
|
-
data: { $schema: "https://svelte.dev/opencode/schema.json" }
|
|
7095
|
-
}]
|
|
7096
|
-
},
|
|
7097
|
-
vscode: {
|
|
7098
|
-
agentPath: "AGENTS.md",
|
|
7099
|
-
configPath: ".vscode/mcp.json",
|
|
7100
|
-
mcpOptions: { serversKey: "servers" }
|
|
7101
|
-
},
|
|
7102
|
-
other: { other: true }
|
|
7103
|
-
};
|
|
7104
|
-
const filesAdded = [];
|
|
7105
|
-
const filesExistingAlready = [];
|
|
7106
|
-
const agentFile = getSharedFiles().filter((file) => file.include.includes("mcp")).find((file) => file.name === "AGENTS.md");
|
|
7107
|
-
for (const ide of options.ide) {
|
|
7108
|
-
const value = configurator[ide];
|
|
7109
|
-
if (value === void 0) continue;
|
|
7110
|
-
if ("other" in value) continue;
|
|
7111
|
-
const { mcpOptions, agentPath, configPath, schema, customData, extraFiles } = value;
|
|
7112
|
-
if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
|
|
7113
|
-
if (content) {
|
|
7114
|
-
filesExistingAlready.push(agentPath);
|
|
7115
|
-
return false;
|
|
7116
|
-
}
|
|
7117
|
-
filesAdded.push(agentPath);
|
|
7118
|
-
return agentFile?.contents ?? "";
|
|
7119
|
-
});
|
|
7120
|
-
sv.file(configPath, transforms.json(({ data }) => {
|
|
7121
|
-
if (schema) data["$schema"] = schema;
|
|
7122
|
-
if (customData) for (const [key, value] of Object.entries(customData)) data[key] = value;
|
|
7123
|
-
if (mcpOptions) {
|
|
7124
|
-
const key = mcpOptions.serversKey ?? "mcpServers";
|
|
7125
|
-
data[key] ??= {};
|
|
7126
|
-
data[key].svelte = options.setup === "local" ? getLocalConfig(mcpOptions) : getRemoteConfig(mcpOptions);
|
|
7127
|
-
}
|
|
7128
|
-
}));
|
|
7129
|
-
if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
|
|
7130
|
-
for (const [key, value] of Object.entries(extra.data)) data[key] = value;
|
|
7131
|
-
}));
|
|
7132
|
-
}
|
|
7133
|
-
if (filesExistingAlready.length > 0) R$1.warn(`${filesExistingAlready.map((path) => color.path(path)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${color.website("https://svelte.dev/docs/mcp/overview#Usage")} for manual setup.`);
|
|
7134
|
-
},
|
|
7135
|
-
nextSteps({ options }) {
|
|
7136
|
-
const steps = [];
|
|
7137
|
-
if (options.ide.includes("other")) steps.push(`For other clients: ${color.website(`https://svelte.dev/docs/mcp/${options.setup}-setup#Other-clients`)}`);
|
|
7138
|
-
return steps;
|
|
7139
|
-
}
|
|
7140
|
-
});
|
|
7295
|
+
/** Workspace-relative source files under `src`, for the `$lib` -> `#lib` rewrite. */
|
|
7296
|
+
function sourceFiles(cwd, src) {
|
|
7297
|
+
const root = path.resolve(cwd, src);
|
|
7298
|
+
if (!fs.existsSync(root)) return [];
|
|
7299
|
+
return fs.readdirSync(root, { recursive: true }).map((entry) => path.join(src, entry)).filter((relative) => SOURCE_EXTENSIONS.some((ext) => relative.endsWith(ext)) && fs.statSync(path.resolve(cwd, relative)).isFile());
|
|
7300
|
+
}
|
|
7141
7301
|
//#endregion
|
|
7142
7302
|
//#region src/addons/mdsvex.ts
|
|
7143
7303
|
var mdsvex_default = defineAddon({
|
|
@@ -7206,11 +7366,15 @@ var paraglide_default = defineAddon({
|
|
|
7206
7366
|
shortDescription: "i18n",
|
|
7207
7367
|
homepage: "https://inlang.com/m/gerre34r/library-inlang-paraglideJs",
|
|
7208
7368
|
options: options$3,
|
|
7209
|
-
setup: ({ isKit, unsupported }) => {
|
|
7369
|
+
setup: ({ isKit, unsupported, runsAfter }) => {
|
|
7210
7370
|
if (!isKit) unsupported("Requires SvelteKit");
|
|
7371
|
+
runsAfter("experimental");
|
|
7211
7372
|
},
|
|
7212
|
-
run: ({ sv, options, file, language, directory }) => {
|
|
7373
|
+
run: ({ sv, options, file, language, directory, dependencyVersion }) => {
|
|
7213
7374
|
const [ts] = createPrinter(language === "ts");
|
|
7375
|
+
const kitRange = dependencyVersion("@sveltejs/kit");
|
|
7376
|
+
const lib = resolveLibPrefix(kitRange);
|
|
7377
|
+
const pathType = isKit3(kitRange) ? "Path" : "Pathname";
|
|
7214
7378
|
const paraglideOutDir = `${directory.lib}/paraglide`;
|
|
7215
7379
|
sv.devDependency("@inlang/paraglide-js", "^2.18.2");
|
|
7216
7380
|
sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
|
|
@@ -7227,7 +7391,7 @@ var paraglide_default = defineAddon({
|
|
|
7227
7391
|
}));
|
|
7228
7392
|
sv.file(`src/hooks.${language}`, transforms.script(({ ast, comments, js }) => {
|
|
7229
7393
|
js.imports.addNamed(ast, {
|
|
7230
|
-
from:
|
|
7394
|
+
from: `${lib}/paraglide/runtime`,
|
|
7231
7395
|
imports: ["deLocalizeUrl"]
|
|
7232
7396
|
});
|
|
7233
7397
|
const expression = js.common.parseExpression("(request) => deLocalizeUrl(request.url).pathname");
|
|
@@ -7250,11 +7414,11 @@ var paraglide_default = defineAddon({
|
|
|
7250
7414
|
}));
|
|
7251
7415
|
sv.file(`src/hooks.server.${language}`, transforms.script(({ ast, comments, js }) => {
|
|
7252
7416
|
js.imports.addNamed(ast, {
|
|
7253
|
-
from:
|
|
7417
|
+
from: `${lib}/paraglide/server`,
|
|
7254
7418
|
imports: ["paraglideMiddleware"]
|
|
7255
7419
|
});
|
|
7256
7420
|
js.imports.addNamed(ast, {
|
|
7257
|
-
from:
|
|
7421
|
+
from: `${lib}/paraglide/runtime`,
|
|
7258
7422
|
imports: ["getTextDirection"]
|
|
7259
7423
|
});
|
|
7260
7424
|
js.kit.addHooksHandle(ast, {
|
|
@@ -7294,7 +7458,7 @@ var paraglide_default = defineAddon({
|
|
|
7294
7458
|
sv.file(`${directory.kitRoutes}/+layout.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
|
|
7295
7459
|
js.imports.addNamed(ast.instance.content, {
|
|
7296
7460
|
imports: ["locales", "localizeHref"],
|
|
7297
|
-
from:
|
|
7461
|
+
from: `${lib}/paraglide/runtime`
|
|
7298
7462
|
});
|
|
7299
7463
|
js.imports.addNamed(ast.instance.content, {
|
|
7300
7464
|
imports: ["page"],
|
|
@@ -7305,14 +7469,14 @@ var paraglide_default = defineAddon({
|
|
|
7305
7469
|
from: "$app/paths"
|
|
7306
7470
|
});
|
|
7307
7471
|
if (language === "ts") js.imports.addNamed(ast.instance.content, {
|
|
7308
|
-
imports: [
|
|
7472
|
+
imports: [pathType],
|
|
7309
7473
|
from: "$app/types",
|
|
7310
7474
|
isType: true
|
|
7311
7475
|
});
|
|
7312
7476
|
svelte.addFragment(ast, dedent`
|
|
7313
7477
|
<div style="display:none">
|
|
7314
7478
|
{#each locales as locale (locale)}
|
|
7315
|
-
<a href={resolve(localizeHref(page.url.pathname, { locale })${ts(
|
|
7479
|
+
<a href={resolve(localizeHref(page.url.pathname, { locale })${ts(` as ${pathType}`)})}>{locale}</a>
|
|
7316
7480
|
{/each}
|
|
7317
7481
|
</div>`, { language });
|
|
7318
7482
|
}));
|
|
@@ -7321,11 +7485,11 @@ var paraglide_default = defineAddon({
|
|
|
7321
7485
|
sv.file(`${directory.kitRoutes}/demo/paraglide/+page.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
|
|
7322
7486
|
js.imports.addNamed(ast.instance.content, {
|
|
7323
7487
|
imports: { m: "m" },
|
|
7324
|
-
from:
|
|
7488
|
+
from: `${lib}/paraglide/messages.js`
|
|
7325
7489
|
});
|
|
7326
7490
|
js.imports.addNamed(ast.instance.content, {
|
|
7327
7491
|
imports: { setLocale: "setLocale" },
|
|
7328
|
-
from:
|
|
7492
|
+
from: `${lib}/paraglide/runtime`
|
|
7329
7493
|
});
|
|
7330
7494
|
let templateCode = "<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>";
|
|
7331
7495
|
const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
|
|
@@ -7603,7 +7767,7 @@ var sveltekit_adapter_default = defineAddon({
|
|
|
7603
7767
|
});
|
|
7604
7768
|
if (adapter.package === "@sveltejs/adapter-cloudflare") {
|
|
7605
7769
|
sv.devDependency("wrangler", "^4.97.0");
|
|
7606
|
-
if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("workerd"
|
|
7770
|
+
if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("workerd"));
|
|
7607
7771
|
const ext = fileExists(cwd, "wrangler.toml") ? "toml" : "jsonc";
|
|
7608
7772
|
const applyWranglerConfig = (data) => {
|
|
7609
7773
|
if (ext === "jsonc") data.$schema ??= "./node_modules/wrangler/config-schema.json";
|
|
@@ -7679,11 +7843,10 @@ var tailwindcss_default = defineAddon({
|
|
|
7679
7843
|
default: [],
|
|
7680
7844
|
required: false
|
|
7681
7845
|
}).build(),
|
|
7682
|
-
run: ({ sv, options, file, isKit, directory, dependencyVersion, language
|
|
7846
|
+
run: ({ sv, options, file, isKit, directory, dependencyVersion, language }) => {
|
|
7683
7847
|
const prettierInstalled = Boolean(dependencyVersion("prettier"));
|
|
7684
7848
|
sv.devDependency("tailwindcss", "^4.3.0");
|
|
7685
7849
|
sv.devDependency("@tailwindcss/vite", "^4.3.0");
|
|
7686
|
-
if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("@tailwindcss/oxide"));
|
|
7687
7850
|
if (prettierInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.8.0");
|
|
7688
7851
|
for (const plugin of plugins) {
|
|
7689
7852
|
if (!options.plugins.includes(plugin.id)) continue;
|
|
@@ -7921,7 +8084,7 @@ const officialAddons$1 = {
|
|
|
7921
8084
|
mdsvex: mdsvex_default,
|
|
7922
8085
|
paraglide: paraglide_default,
|
|
7923
8086
|
storybook: storybook_default,
|
|
7924
|
-
|
|
8087
|
+
aiTools: ai_tools_default,
|
|
7925
8088
|
experimental: experimental_default
|
|
7926
8089
|
};
|
|
7927
8090
|
function getAddonDetails(id) {
|
|
@@ -10737,31 +10900,40 @@ async function formatFiles(options) {
|
|
|
10737
10900
|
const { start, stop } = vt();
|
|
10738
10901
|
start("Formatting modified files");
|
|
10739
10902
|
const args = [
|
|
10740
|
-
"prettier",
|
|
10741
10903
|
"--write",
|
|
10742
10904
|
"--ignore-unknown",
|
|
10743
10905
|
...options.filesToFormat
|
|
10744
10906
|
];
|
|
10745
|
-
|
|
10907
|
+
let result = await run("prettier", args, options.cwd);
|
|
10908
|
+
if (result.notFound) {
|
|
10909
|
+
const cmd = resolveCommand(options.packageManager, "execute-local", ["prettier", ...args]);
|
|
10910
|
+
result = await run(cmd.command, cmd.args, options.cwd);
|
|
10911
|
+
}
|
|
10912
|
+
if (result.error !== void 0) {
|
|
10913
|
+
stop("Failed to format files");
|
|
10914
|
+
R$1.error(result.error);
|
|
10915
|
+
return;
|
|
10916
|
+
}
|
|
10917
|
+
stop("Successfully formatted modified files");
|
|
10918
|
+
}
|
|
10919
|
+
async function run(command, args, cwd) {
|
|
10746
10920
|
try {
|
|
10747
|
-
|
|
10921
|
+
await z(command, args, {
|
|
10748
10922
|
nodeOptions: {
|
|
10749
|
-
cwd
|
|
10923
|
+
cwd,
|
|
10750
10924
|
stdio: "pipe"
|
|
10751
10925
|
},
|
|
10752
10926
|
throwOnError: true
|
|
10753
10927
|
});
|
|
10754
|
-
|
|
10755
|
-
stop("Failed to format files");
|
|
10756
|
-
R$1.error(result.stderr);
|
|
10757
|
-
return;
|
|
10758
|
-
}
|
|
10928
|
+
return {};
|
|
10759
10929
|
} catch (e) {
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10930
|
+
if (e?.code === "ENOENT") return {
|
|
10931
|
+
notFound: true,
|
|
10932
|
+
error: `${command} not found`
|
|
10933
|
+
};
|
|
10934
|
+
const output = e?.output;
|
|
10935
|
+
return { error: [output?.stderr, output?.stdout].filter(Boolean).join("\n").trim() || (e instanceof Error ? e.message : "unknown error") };
|
|
10763
10936
|
}
|
|
10764
|
-
stop("Successfully formatted modified files");
|
|
10765
10937
|
}
|
|
10766
10938
|
//#endregion
|
|
10767
10939
|
//#region src/core/package-manager.ts
|
|
@@ -12362,10 +12534,30 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions }
|
|
|
12362
12534
|
cancels
|
|
12363
12535
|
};
|
|
12364
12536
|
}
|
|
12537
|
+
/**
|
|
12538
|
+
* Orders add-ons so every `runsAfter` is honoured, keeping the original order between add-ons that
|
|
12539
|
+
* don't constrain each other. Cycles and unknown ids are ignored rather than fatal - an add-on that
|
|
12540
|
+
* can't be placed simply keeps its position.
|
|
12541
|
+
*/
|
|
12365
12542
|
function orderAddons(addons, setupResults) {
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12543
|
+
const byId = new Map(addons.map((addon) => [addon.id, addon]));
|
|
12544
|
+
const ordered = [];
|
|
12545
|
+
const placed = /* @__PURE__ */ new Set();
|
|
12546
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
12547
|
+
const place = (addon) => {
|
|
12548
|
+
if (placed.has(addon.id) || visiting.has(addon.id)) return;
|
|
12549
|
+
visiting.add(addon.id);
|
|
12550
|
+
for (const id of setupResults[addon.id]?.runsAfter ?? []) {
|
|
12551
|
+
const dependency = byId.get(id);
|
|
12552
|
+
if (dependency) place(dependency);
|
|
12553
|
+
}
|
|
12554
|
+
visiting.delete(addon.id);
|
|
12555
|
+
placed.add(addon.id);
|
|
12556
|
+
ordered.push(addon);
|
|
12557
|
+
};
|
|
12558
|
+
const seeded = [...addons].sort((a, b) => (setupResults[a.id]?.runsAfter?.length ?? 0) - (setupResults[b.id]?.runsAfter?.length ?? 0));
|
|
12559
|
+
for (const addon of seeded) place(addon);
|
|
12560
|
+
return ordered;
|
|
12369
12561
|
}
|
|
12370
12562
|
//#endregion
|
|
12371
12563
|
export { __toESM as S, name as _, detectPackageManager as a, __commonJSMin as b, defineAddonOptions as c, Command as d, R as f, helpConfig as g, forwardExitCode as h, addPnpmAllowBuilds as i, from as l, create$1 as m, add$1 as n, officialAddons$1 as o, z as p, create as r, defineAddon as s, add as t, program as u, version as v, __require as x, svDeprecated as y };
|
package/dist/shared.json
CHANGED
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
"exclude": [],
|
|
23
23
|
"contents": "# [sv](https://svelte.dev/docs/cli/overview) community add-on: [~SV-NAME-TODO~](https://github.com/~SV-NAME-TODO~)\n\n> [!IMPORTANT]\n> Svelte maintainers have not reviewed community add-ons for malicious code! Use at your discretion.\n\n## Usage\n\nTo install the add-on, run:\n\n```shell\nnpx sv add ~SV-PROTOCOL-NAME-TODO~\n```\n\n## What you get [TO BE FILLED...]\n\n- A super cool stuff\n- Another one!\n\n## Options [TO BE FILLED...]\n\n### `who`\n\nThe name of the person to say hello to.\n\nDefault: `you`\n\n```shell\nnpx sv add ~SV-PROTOCOL-NAME-TODO~=\"who:your-name\"\n```\n"
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
"name": "svelte-file-editor.md",
|
|
27
|
+
"include": [
|
|
28
|
+
"agents"
|
|
29
|
+
],
|
|
30
|
+
"exclude": [],
|
|
31
|
+
"contents": "---\nname: svelte-file-editor\ndescription: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.\n---\n\nYou are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the Svelte MCP server to fetch documentation with `get_documentation` and validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.\n\nIf the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.\n\nIf the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.\n\n## Available MCP tools\n\n### 1. list-sections\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.\n\n### 2. get-documentation\n\nRetrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.\n\n**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`\n\n### 3. svelte-autofixer\n\nAnalyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:\n\n- Using `$effect` instead of `$derived` for computations\n- Missing cleanup in effects\n- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)\n- Missing keys in `{#each}` blocks\n- And more\n\n## Workflow\n\nWhen invoked to work on a Svelte file:\n\n### 1. Gather context (if needed)\n\nIf you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:\n\n1. Call `list-sections` to see available documentation\n2. Call `get-documentation` with relevant section names\n\n### 2. Read the target file\n\nRead the file to understand the current implementation.\n\n### 3. Make changes\n\nApply edits following Svelte 5 best practices:\n\n### 4. Validate changes\n\nAfter editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.\n\n### 5. Fix any issues\n\nIf the autofixer reports problems, fix them and re-validate until no issues remain.\n\n## Output format\n\nAfter completing your work, provide:\n\n1. Summary of changes made\n2. Any issues found and fixed by the autofixer\n3. Recommendations for further improvements (if any)\n"
|
|
32
|
+
},
|
|
25
33
|
{
|
|
26
34
|
"name": "jsconfig.json",
|
|
27
35
|
"include": [
|
|
@@ -70,6 +78,94 @@
|
|
|
70
78
|
"exclude": [],
|
|
71
79
|
"contents": "<script lang=\"ts\">\n\timport favicon from '$lib/assets/favicon.svg';\n\n\tlet { children } = $props();\n\n\tconst title = '$sv-title-$sv';\n\tconst href = '$sv-url-$sv';\n\n\tlet prefersDark = $state(true);\n\tlet isDark = $state(true);\n\n\tfunction switchTheme() {\n\t\tconst value = isDark ? 'light' : 'dark';\n\n\t\tisDark = value === 'dark';\n\t\tlocalStorage.setItem('sv:theme', isDark === prefersDark ? 'system' : value);\n\t}\n\n\t$effect(() => {\n\t\tdocument.documentElement.classList.remove('light', 'dark');\n\t\tprefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\n\t\tconst theme = localStorage.getItem('sv:theme');\n\n\t\tisDark = !theme ? prefersDark : theme === 'dark' || (theme === 'system' && prefersDark);\n\t\tdocument.documentElement.classList.add(isDark ? 'dark' : 'light');\n\t});\n</script>\n\n<svelte:head>\n\t<title>--from-playground {title}</title>\n\t<script>\n\t\t{\n\t\t\tconst theme = localStorage.getItem('sv:theme');\n\n\t\t\tdocument.documentElement.classList.add(\n\t\t\t\t!theme || theme === 'system'\n\t\t\t\t\t? window.matchMedia('(prefers-color-scheme: dark)').matches\n\t\t\t\t\t\t? 'dark'\n\t\t\t\t\t\t: 'light'\n\t\t\t\t\t: theme\n\t\t\t);\n\t\t}\n\t</script>\n</svelte:head>\n\n<div class=\"layout\">\n\t<nav class=\"navbar\">\n\t\t<div class=\"nav-left\">\n\t\t\t<a href=\"/\" class=\"svelte-icon\">\n\t\t\t\t<img src={favicon} alt=\"Svelte\" width=\"32\" height=\"32\" />\n\t\t\t</a>\n\t\t\t<p class=\"title\">{title}</p>\n\t\t</div>\n\t\t<div class=\"nav-right\">\n\t\t\t<a {href} class=\"raised\" target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t\t--to-playground\n\t\t\t\t<span aria-hidden=\"true\" style=\"margin-left:0.25em;\"> ↗</span>\n\t\t\t</a>\n\t\t\t<button class=\"raised theme-toggle\" onclick={switchTheme} aria-label=\"Toggle theme\">\n\t\t\t\t<span class=\"icon\"></span>\n\t\t\t</button>\n\t\t</div>\n\t</nav>\n\n\t<main class=\"content\">\n\t\t{@render children()}\n\t</main>\n</div>\n\n<style>\n\t:global(body) {\n\t\tmargin: 0;\n\t}\n\n\t:global(html) {\n\t\tmargin: 0;\n\t\t--bg-1: hsl(0, 0%, 100%);\n\t\t--bg-2: hsl(206, 20%, 90%);\n\t\t--bg-3: hsl(206, 20%, 80%);\n\t\t--navbar-bg: #fff;\n\t\t--fg-1: hsl(0, 0%, 13%);\n\t\t--fg-2: hsl(0, 0%, 50%);\n\t\t--fg-3: hsl(0, 0%, 60%);\n\t\t--link: hsl(208, 77%, 47%);\n\t\t--border-radius: 4px;\n\t\t--font:\n\t\t\t-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',\n\t\t\t'Helvetica Neue', sans-serif;\n\t\tcolor-scheme: light;\n\t\tbackground: var(--bg-1);\n\t\tcolor: var(--fg-1);\n\t\tfont-family: var(--font);\n\t\tline-height: 1.5;\n\t\theight: calc(100vh - 2rem);\n\t\taccent-color: var(--link) !important;\n\t\tmin-height: 100vh;\n\t\tbackground-color: var(--bg-1);\n\t}\n\n\t:global(html.dark) {\n\t\tcolor-scheme: dark;\n\t\t--bg-1: hsl(0, 0%, 18%);\n\t\t--bg-2: hsl(0, 0%, 30%);\n\t\t--bg-3: hsl(0, 0%, 40%);\n\t\t--navbar-bg: hsl(220, 14%, 16%);\n\t\t--fg-1: hsl(0, 0%, 75%);\n\t\t--fg-2: hsl(0, 0%, 40%);\n\t\t--fg-3: hsl(0, 0%, 30%);\n\t\t--link: hsl(206, 96%, 72%);\n\t}\n\n\t.navbar {\n\t\tcolor: var(--fg-1);\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\tpadding: 0em 2.5rem;\n\t\theight: 3.7rem;\n\t\tbackground-color: var(--navbar-bg);\n\t\tbox-shadow:\n\t\t\t0 2px 8px 0 rgba(0, 0, 0, 0.08),\n\t\t\t0 1.5px 4px 0 rgba(0, 0, 0, 0.04);\n\t}\n\n\t.nav-left {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.5rem;\n\t}\n\n\t.svelte-icon {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\ttext-decoration: none;\n\t\ttransition: opacity 0.2s ease;\n\t}\n\n\t.svelte-icon:hover {\n\t\topacity: 0.8;\n\t}\n\n\t.title {\n\t\tfont-size: 1.5rem;\n\t\tfont-weight: 400;\n\t\tmargin: 0;\n\t}\n\n\t.nav-right {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 1rem;\n\t}\n\n\t.raised {\n\t\tbackground: var(--navbar-bg);\n\t\tborder-left: 0.5px solid var(--fg-3);\n\t\tborder-top: 0.5px solid var(--fg-3);\n\t\tborder-bottom: none;\n\t\tborder-right: none;\n\t\tborder-radius: var(--border-radius);\n\t\tcolor: var(--fg-1);\n\t\tcursor: pointer;\n\t\ttransition: all 0.2s ease;\n\t\tbox-shadow:\n\t\t\t0 2px 4px rgba(0, 0, 0, 0.1),\n\t\t\t0 1px 2px rgba(0, 0, 0, 0.06);\n\t\ttext-decoration: none;\n\t\tfont-weight: 500;\n\t\tpadding: 0.25rem 0.75rem;\n\t\tfont-size: 0.8rem;\n\t}\n\n\t.raised:hover {\n\t\tborder-left-color: var(--fg-2);\n\t\tborder-top-color: var(--fg-2);\n\t\tbox-shadow:\n\t\t\t0 4px 8px rgba(0, 0, 0, 0.15),\n\t\t\t0 2px 4px rgba(0, 0, 0, 0.1);\n\t\ttransform: translate(-1px, -1px);\n\t}\n\n\t.content {\n\t\tpadding: 1rem;\n\t\tcolor: var(--fg-1);\n\t}\n\n\t.theme-toggle {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 1.8rem;\n\t\theight: 1.8rem;\n\t\tpadding: 0;\n\t\tmin-width: 2rem;\n\t}\n\n\t.icon {\n\t\tdisplay: inline-block;\n\t\twidth: 1.5rem;\n\t\theight: 1.5rem;\n\t\t-webkit-mask-size: 1.5rem;\n\t\tmask-size: 1.5rem;\n\t\t-webkit-mask-repeat: no-repeat;\n\t\tmask-repeat: no-repeat;\n\t\t-webkit-mask-position: center;\n\t\tmask-position: center;\n\t\tbackground-color: var(--fg-1);\n\t}\n\n\t.icon {\n\t\tmask-image: url('data:image/svg+xml,%3csvg%20xmlns=\"http://www.w3.org/2000/svg\"%20viewBox=\"0%200%2024%2024\"%3e%3cpath%20fill=\"%23666\"%20d=\"M12%2021q-3.775%200-6.388-2.613T3%2012q0-3.45%202.25-5.988T11%203.05q.625-.075.975.45t-.025%201.1q-.425.65-.638%201.375T11.1%207.5q0%202.25%201.575%203.825T16.5%2012.9q.775%200%201.538-.225t1.362-.625q.525-.35%201.075-.037t.475.987q-.35%203.45-2.937%205.725T12%2021Zm0-2q2.2%200%203.95-1.213t2.55-3.162q-.5.125-1%20.2t-1%20.075q-3.075%200-5.238-2.163T9.1%207.5q0-.5.075-1t.2-1q-1.95.8-3.163%202.55T5%2012q0%202.9%202.05%204.95T12%2019Zm-.25-6.75Z\"/%3e%3c/svg%3e');\n\t}\n\n\t:global(html.dark) .icon {\n\t\tmask-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3e%3cpath%20fill='%23d4d4d4'%20d='M12%2019a1%201%200%200%201%20.993.883L13%2020v1a1%201%200%200%201-1.993.117L11%2021v-1a1%201%200%200%201%201-1zm6.313-2.09.094.083.7.7a1%201%200%200%201-1.32%201.497l-.094-.083-.7-.7a1%201%200%200%201%201.218-1.567l.102.07zm-11.306.083a1%201%200%200%201%20.083%201.32l-.083.094-.7.7a1%201%200%200%201-1.497-1.32l.083-.094.7-.7a1%201%200%200%201%201.414%200zM4%2011a1%201%200%200%201%20.117%201.993L4%2013H3a1%201%200%200%201-.117-1.993L3%2011h1zm17%200a1%201%200%200%201%20.117%201.993L21%2013h-1a1%201%200%200%201-.117-1.993L20%2011h1zM6.213%204.81l.094.083.7.7a1%201%200%200%201-1.32%201.497l-.094-.083-.7-.7A1%201%200%200%201%206.11%204.74l.102.07zm12.894.083a1%201%200%200%201%20.083%201.32l-.083.094-.7.7a1%201%200%200%201-1.497-1.32l.083-.094.7-.7a1%201%200%200%201%201.414%200zM12%202a1%201%200%200%201%20.993.883L13%203v1a1%201%200%200%201-1.993.117L11%204V3a1%201%200%200%201%201-1zm0%205a5%205%200%201%201-4.995%205.217L7%2012l.005-.217A5%205%200%200%201%2012%207z'/%3e%3c/svg%3e\");\n\t}\n</style>\n"
|
|
72
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"name": "svelte-code-writer/SKILL.md",
|
|
83
|
+
"include": [
|
|
84
|
+
"skills"
|
|
85
|
+
],
|
|
86
|
+
"exclude": [],
|
|
87
|
+
"contents": "---\nname: svelte-code-writer\ndescription: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.\n---\n\n## CLI tools\n\nYou have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:\n\n### List documentation sections\n\n```bash\nnpx @sveltejs/mcp list-sections\n```\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths.\n\n### Get documentation\n\n```bash\nnpx @sveltejs/mcp get-documentation \"<section1>,<section2>,...\"\n```\n\nRetrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.\n\n**Example:**\n\n```bash\nnpx @sveltejs/mcp get-documentation \"$state,$derived,$effect\"\n```\n\n### Svelte autofixer\n\n```bash\nnpx @sveltejs/mcp svelte-autofixer \"<code_or_path>\" [options]\n```\n\nAnalyzes Svelte code and suggests fixes for common issues.\n\n**Options:**\n\n- `--async` - Enable async Svelte mode (default: false)\n- `--svelte-version` - Target version: 4 or 5 (default: 5)\n\n**Examples:**\n\n```bash\n# Analyze inline code (escape $ as \\$)\nnpx @sveltejs/mcp svelte-autofixer '<script>let count = \\$state(0);</script>'\n\n# Analyze a file\nnpx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte\n\n# Target Svelte 4\nnpx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4\n```\n\n**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\\$` to prevent shell variable substitution.\n\n## Workflow\n\n1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics\n2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues\n3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component\n"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "svelte-core-bestpractices/SKILL.md",
|
|
91
|
+
"include": [
|
|
92
|
+
"skills"
|
|
93
|
+
],
|
|
94
|
+
"exclude": [],
|
|
95
|
+
"contents": "---\nname: svelte-core-bestpractices\ndescription: Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.\n---\n\n## `$state`\n\nOnly use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.\n\nObjects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.\n\n## `$derived`\n\nTo compute something from state, use `$derived` rather than `$effect`:\n\n```js\n// do this\nlet square = $derived(num * num);\n\n// don't do this\nlet square;\n\n$effect(() => {\n\tsquare = num * num;\n});\n```\n\n> [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.\n\nDeriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.\n\nIf the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.\n\n## `$effect`\n\nEffects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.\n\n- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)\n- If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate\n- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)\n- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)\n\nNever wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.\n\n## `$props`\n\nTreat props as though they will change. For example, values that depend on props should usually use `$derived`:\n\n```js\n// @errors: 2451\nlet { type } = $props();\n\n// do this\nlet color = $derived(type === 'danger' ? 'red' : 'green');\n\n// don't do this — `color` will not update if `type` changes\nlet color = type === 'danger' ? 'red' : 'green';\n```\n\n## `$inspect.trace`\n\n`$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.\n\n## Events\n\nAny element attribute starting with `on` is treated as an event listener:\n\n```svelte\n<button onclick={() => {...}}>click me</button>\n\n<!-- attribute shorthand also works -->\n<button {onclick}>...</button>\n\n<!-- so do spread attributes -->\n<button {...props}>...</button>\n```\n\nIf you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:\n\n```svelte\n<svelte:window onkeydown={...} />\n<svelte:document onvisibilitychange={...} />\n```\n\nAvoid using `onMount` or `$effect` for this.\n\n## Snippets\n\n[Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/render.md) tag, or passed to components as props. They must be declared within the template.\n\n```svelte\n{#snippet greeting(name)}\n\t<p>hello {name}!</p>\n{/snippet}\n\n{@render greeting('world')}\n```\n\n> [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.\n\n## Each blocks\n\nPrefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.\n\n> [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.\n\nAvoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).\n\n## Using JavaScript variables in CSS\n\nIf you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.\n\n```svelte\n<div style:--columns={columns}>...</div>\n```\n\nYou can then reference `var(--columns)` inside the component's `<style>`.\n\n## Styling child components\n\nThe CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:\n\n```svelte\n<!-- Parent.svelte -->\n<Child --color=\"red\" />\n\n<!-- Child.svelte -->\n<h1>Hello</h1>\n\n<style>\n\th1 {\n\t\tcolor: var(--color);\n\t}\n</style>\n```\n\nIf this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:\n\n```svelte\n<div>\n\t<Child />\n</div>\n\n<style>\n\tdiv :global {\n\t\th1 {\n\t\t\tcolor: red;\n\t\t}\n\t}\n</style>\n```\n\n## Context\n\nConsider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.\n\nUse `createContext` rather than `setContext` and `getContext`, as it provides type safety.\n\n## Async Svelte\n\nIf using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.\n\n## Avoid legacy features\n\nAlways use runes mode for new code, and avoid features that have more modern replacements:\n\n- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)\n- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)\n- use `$props` instead of `export let`, `$$props` and `$$restProps`\n- use `onclick={...}` instead of `on:click={...}`\n- use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`\n- use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`\n- use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`\n- use classes with `$state` fields to share reactivity between components, instead of using stores\n- use `{@attach ...}` instead of `use:action`\n- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive\n"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "svelte-core-bestpractices/references/attach.md",
|
|
99
|
+
"include": [
|
|
100
|
+
"skills"
|
|
101
|
+
],
|
|
102
|
+
"exclude": [],
|
|
103
|
+
"contents": "Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) read inside the function updates.\n\nOptionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM.\n\n> [!NOTE]\n> Attachments are available in Svelte 5.29 and newer.\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\t/** @type {import('svelte/attachments').Attachment} */\n\tfunction myAttachment(element) {\n\t\tconsole.log(element.nodeName); // 'DIV'\n\n\t\treturn () => {\n\t\t\tconsole.log('cleaning up');\n\t\t};\n\t}\n</script>\n\n<div {@attach myAttachment}>...</div>\n```\n\nAn element can have any number of attachments.\n\n## Attachment factories\n\nA useful pattern is for a function, such as `tooltip` in this example, to _return_ an attachment (demo:\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport tippy from 'tippy.js';\n\n\tlet content = $state('Hello!');\n\n\t/**\n\t * @param {string} content\n\t * @returns {import('svelte/attachments').Attachment}\n\t */\n\tfunction tooltip(content) {\n\t\treturn (element) => {\n\t\t\tconst tooltip = tippy(element, { content });\n\t\t\treturn tooltip.destroy;\n\t\t};\n\t}\n</script>\n\n<input bind:value={content} />\n\n<button {@attach tooltip(content)}>\n\tHover me\n</button>\n```\n\nSince the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), the attachment will be destroyed and recreated whenever `content` changes. The same thing would happen for any state read _inside_ the attachment function when it first runs. (If this isn't what you want, see [Controlling when attachments re-run](#Controlling-when-attachments-re-run).)\n\n## Inline attachments\n\nAttachments can also be created inline (demo:\n\n```svelte\n<!--- file: App.svelte --->\n<canvas\n\twidth={32}\n\theight={32}\n\t{@attach (canvas) => {\n\t\tconst context = canvas.getContext('2d');\n\n\t\t$effect(() => {\n\t\t\tcontext.fillStyle = color;\n\t\t\tcontext.fillRect(0, 0, canvas.width, canvas.height);\n\t\t});\n\t}}\n></canvas>\n```\n\n> [!NOTE]\n> The nested effect runs whenever `color` changes, while the outer effect (where `canvas.getContext(...)` is called) only runs once, since it doesn't read any reactive state.\n\n## Conditional attachments\n\nFalsy values like `false` or `undefined` are treated as no attachment, enabling conditional usage:\n\n```svelte\n<div {@attach enabled && myAttachment}>...</div>\n```\n\n## Passing attachments to components\n\nWhen used on a component, `{@attach ...}` will create a prop whose key is a [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). If the component then [spreads](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.\n\nThis allows you to create _wrapper components_ that augment elements (demo:\n\n```svelte\n<!--- file: Button.svelte --->\n<script>\n\t/** @type {import('svelte/elements').HTMLButtonAttributes} */\n\tlet { children, ...props } = $props();\n</script>\n\n<!-- `props` includes attachments -->\n<button {...props}>\n\t{@render children?.()}\n</button>\n```\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport tippy from 'tippy.js';\n\timport Button from './Button.svelte';\n\n\tlet content = $state('Hello!');\n\n\t/**\n\t * @param {string} content\n\t * @returns {import('svelte/attachments').Attachment}\n\t */\n\tfunction tooltip(content) {\n\t\treturn (element) => {\n\t\t\tconst tooltip = tippy(element, { content });\n\t\t\treturn tooltip.destroy;\n\t\t};\n\t}\n</script>\n\n<input bind:value={content} />\n\n<Button {@attach tooltip(content)}>\n\tHover me\n</Button>\n```\n\n## Controlling when attachments re-run\n\nAttachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), are fully reactive: `{@attach foo(bar)}` will re-run on changes to `foo` _or_ `bar` (or any state read inside `foo`):\n\n```js\n// @errors: 7006 2304 2552\nfunction foo(bar) {\n\treturn (node) => {\n\t\tveryExpensiveSetupWork(node);\n\t\tupdate(node, bar);\n\t};\n}\n```\n\nIn the rare case that this is a problem (for example, if `foo` does expensive and unavoidable setup work) consider passing the data inside a function and reading it in a child effect:\n\n```js\n// @errors: 7006 2304 2552\nfunction foo(+++getBar+++) {\n\treturn (node) => {\n\t\tveryExpensiveSetupWork(node);\n\n+++\t\t$effect(() => {\n\t\t\tupdate(node, getBar());\n\t\t});+++\n\t}\n}\n```\n\n## Creating attachments programmatically\n\nTo add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#createAttachmentKey).\n\n## Converting actions to attachments\n\nIf you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.\n"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "svelte-core-bestpractices/references/await-expressions.md",
|
|
107
|
+
"include": [
|
|
108
|
+
"skills"
|
|
109
|
+
],
|
|
110
|
+
"exclude": [],
|
|
111
|
+
"contents": "As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable:\n\n- at the top level of your component's `<script>`\n- inside `$derived(...)` declarations\n- inside your markup\n\nThis feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:\n\n```js\n/// file: svelte.config.js\nexport default {\n\tcompilerOptions: {\n\t\texperimental: {\n\t\t\tasync: true\n\t\t}\n\t}\n};\n```\n\nThe experimental flag will be removed in Svelte 6.\n\n## Synchronized updates\n\nWhen an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like this...\n\n<!-- codeblock:start {\"title\":\"Synchronized updates\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet a = $state(1);\n\tlet b = $state(2);\n\n\tasync function add(a, b) {\n\t\tawait new Promise((f) => setTimeout(f, 500)); // artificial delay\n\t\treturn a + b;\n\t}\n</script>\n\n<input type=\"number\" bind:value={a}>\n<input type=\"number\" bind:value={b}>\n\n<p>{a} + {b} = {await add(a, b)}</p>\n```\n\n<!-- codeblock:end -->\n\n...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —\n\n```html\n<p>2 + 2 = 3</p>\n```\n\n— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves.\n\nUpdates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.\n\n## Concurrency\n\nSvelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...\n\n```svelte\n<p>{await one(x)}</p>\n<p>{await two(y)}</p>\n```\n\n...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.\n\nThis does not apply to sequential `await` expressions inside your `<script>` or inside async functions — these run like any other asynchronous JavaScript. An exception is that independent `$derived` expressions will update independently, even though they will run sequentially when they are first created:\n\n```js\n// `b` will not be created until `a` has resolved,\n// but once created they will update independently\n// even if `x` and `y` update simultaneously\nlet a = $derived(await one(x));\nlet b = $derived(await two(y));\n```\n\n> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning\n\n## Indicating loading states\n\nTo render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.\n\nAfter the contents of a boundary have resolved for the first time and have replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$effect.pending). This is what you would use to display a \"we're asynchronously validating your input\" spinner next to a form field, for example.\n\nYou can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:\n\n```js\nimport { tick, settled } from 'svelte';\n\nasync function onclick() {\n\tupdating = true;\n\n\t// without this, the change to `updating` will be\n\t// grouped with the other changes, meaning it\n\t// won't be reflected in the UI\n\tawait tick();\n\n\tcolor = 'octarine';\n\tanswer = 42;\n\n\tawait settled();\n\n\t// any updates affected by `color` or `answer`\n\t// have now been applied\n\tupdating = false;\n}\n```\n\n## Error handling\n\nErrors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).\n\n## Server-side rendering\n\nSvelte supports asynchronous server-side rendering (SSR) with the `render(...)` API. To use it, simply await the return value:\n\n```js\n/// file: server.js\nimport { render } from 'svelte/server';\nimport App from './App.svelte';\n\nconst { head, body } = +++await+++ render(App);\n```\n\n> [!NOTE] If you're using a framework like SvelteKit, this is done on your behalf.\n\nIf a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All `await` expressions encountered outside boundaries with `pending` snippets will resolve and render their contents prior to `await render(...)` returning.\n\n> [!NOTE] In the future, we plan to add a streaming implementation that renders the content in the background.\n\n## Forking\n\nThe [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#fork) API, added in 5.42, makes it possible to run `await` expressions that you _expect_ to happen in the near future. This is mainly intended for frameworks like SvelteKit to implement preloading when (for example) users signal an intent to navigate.\n\n```svelte\n<script>\n\timport { fork } from 'svelte';\n\timport Menu from './Menu.svelte';\n\n\tlet open = $state(false);\n\n\t/** @type {import('svelte').Fork | null} */\n\tlet pending = null;\n\n\tfunction preload() {\n\t\tpending ??= fork(() => {\n\t\t\topen = true;\n\t\t});\n\t}\n\n\tfunction discard() {\n\t\tpending?.discard();\n\t\tpending = null;\n\t}\n</script>\n\n<button\n\tonfocusin={preload}\n\tonfocusout={discard}\n\tonpointerenter={preload}\n\tonpointerleave={discard}\n\tonclick={() => {\n\t\tpending?.commit();\n\t\tpending = null;\n\n\t\t// in case `pending` didn't exist\n\t\t// (if it did, this is a no-op)\n\t\topen = true;\n\t}}\n>open menu</button>\n\n{#if open}\n\t<!-- any async work inside this component will start\n\t as soon as the fork is created -->\n\t<Menu onclose={() => open = false} />\n{/if}\n```\n\n## Caveats\n\nAs an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.\n\n## Breaking changes\n\nEffects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in very rare situations.\n"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "svelte-core-bestpractices/references/bind.md",
|
|
115
|
+
"include": [
|
|
116
|
+
"skills"
|
|
117
|
+
],
|
|
118
|
+
"exclude": [],
|
|
119
|
+
"contents": "## Function bindings\n\nYou can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:\n\n```svelte\n<input bind:value={\n\t() => value,\n\t(v) => value = v.toLowerCase()}\n/>\n```\n\nIn the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:\n\n```svelte\n<div\n\tbind:clientWidth={null, redraw}\n\tbind:clientHeight={null, redraw}\n>...</div>\n```\n\n> [!NOTE]\n> Function bindings are available in Svelte 5.9.0 and newer.\n"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "svelte-core-bestpractices/references/each.md",
|
|
123
|
+
"include": [
|
|
124
|
+
"skills"
|
|
125
|
+
],
|
|
126
|
+
"exclude": [],
|
|
127
|
+
"contents": "## Keyed each blocks\n\n```svelte\n<!--- copy: false --->\n{#each expression as name (key)}...{/each}\n```\n\n```svelte\n<!--- copy: false --->\n{#each expression as name, index (key)}...{/each}\n```\n\nIf a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to intelligently update the list when data changes by inserting, moving and deleting items, rather than adding or removing items at the end and updating the state in the middle.\n\nThe key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.\n\n```svelte\n{#each items as item (item.id)}\n\t<li>{item.name} x {item.qty}</li>\n{/each}\n\n<!-- or with additional index value -->\n{#each items as item, i (item.id)}\n\t<li>{i + 1}: {item.name} x {item.qty}</li>\n{/each}\n```\n\nYou can freely use destructuring and rest patterns in each blocks.\n\n```svelte\n{#each items as { id, name, qty }, i (id)}\n\t<li>{i + 1}: {name} x {qty}</li>\n{/each}\n\n{#each objects as { id, ...rest }}\n\t<li><span>{id}</span><MyComponent {...rest} /></li>\n{/each}\n\n{#each items as [id, ...rest]}\n\t<li><span>{id}</span><MyComponent values={rest} /></li>\n{/each}\n```\n"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "svelte-core-bestpractices/references/hydratable.md",
|
|
131
|
+
"include": [
|
|
132
|
+
"skills"
|
|
133
|
+
],
|
|
134
|
+
"exclude": [],
|
|
135
|
+
"contents": "In Svelte, when you want to render asynchronous content data on the server, you can simply `await` it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:\n\n```svelte\n<script>\n import { getUser } from 'my-database-library';\n\n // This will get the user on the server, render the user's name into the h1,\n // and then, during hydration on the client, it will get the user _again_,\n // blocking hydration until it's done.\n const user = await getUser();\n</script>\n\n<h1>{user.name}</h1>\n```\n\nThat's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](https://svelte.dev/docs/kit/remote-functions/llms.txt).\n\nTo fix the example above:\n\n```svelte\n<script>\n import { hydratable } from 'svelte';\n import { getUser } from 'my-database-library';\n\n // During server rendering, this will serialize and stash the result of `getUser`, associating\n // it with the provided key and baking it into the `head` content. During hydration, it will\n // look for the serialized version, returning it instead of running `getUser`. After hydration\n // is done, if it's called again, it'll simply invoke `getUser`.\n const user = await hydratable('user', () => getUser());\n</script>\n\n<h1>{user.name}</h1>\n```\n\nThis API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:\n\n```ts\nimport { hydratable } from 'svelte';\nconst rand = hydratable('random', () => Math.random());\n```\n\nIf you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries.\n\n## Serialization\n\nAll data returned from a `hydratable` function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses [`devalue`](https://npmjs.com/package/devalue), which can serialize all sorts of things including `Map`, `Set`, `URL`, and `BigInt`. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:\n\n```svelte\n<script>\n import { hydratable } from 'svelte';\n const promises = hydratable('random', () => {\n return {\n one: Promise.resolve(1),\n two: Promise.resolve(2)\n }\n });\n</script>\n\n{await promises.one}\n{await promises.two}\n```\n\n## CSP\n\n`hydratable` adds an inline `<script>` block to the `head` returned from `render`. If you're using [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) (CSP), this script will likely fail to run. You can provide a `nonce` to `render`:\n\n```js\nconst nonce = crypto.randomUUID();\n\nconst { head, body } = await render(App, {\n\tcsp: { nonce }\n});\n```\n\nThis will add the `nonce` to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:\n\n```js\nresponse.headers.set(\n 'Content-Security-Policy',\n `script-src 'nonce-${nonce}'`\n );\n```\n\nIt's essential that a `nonce` — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.\n\nIf instead you are generating static HTML ahead of time, you must use hashes instead:\n\n```js\nconst { head, body, hashes } = await render(App, {\n\tcsp: { hash: true }\n});\n```\n\n`hashes.script` will be an array of strings like `[\"sha256-abcd123\"]`. As with `nonce`, the hashes should be used in your CSP header:\n\n```js\nresponse.headers.set(\n 'Content-Security-Policy',\n `script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`\n );\n```\n\nWe recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.\n"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "svelte-core-bestpractices/references/inspect.md",
|
|
139
|
+
"include": [
|
|
140
|
+
"skills"
|
|
141
|
+
],
|
|
142
|
+
"exclude": [],
|
|
143
|
+
"contents": "> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.\n\nThe `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object or array using fine-grained reactivity will cause it to re-fire:\n\n<!-- codeblock:start {\"title\":\"$inspect(...)\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet count = $state(0);\n\tlet message = $state('hello');\n\n\t$inspect(count, message); // will console.log when `count` or `message` change\n</script>\n\n<button onclick={() => count++}>Increment</button>\n<input bind:value={message} />\n```\n\n<!-- codeblock:end -->\n\nOn updates, a stack trace will be printed, making it easy to find the origin of a state change (unless you're in the playground, due to technical limitations).\n\n## $inspect(...).with\n\n`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that will then be invoked instead of `console.log`. The first argument to the callback is either `\"init\"` or `\"update\"`; subsequent arguments are the values passed to `$inspect`:\n\n<!-- codeblock:start {\"title\":\"$inspect(...).with(...)\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet count = $state(0);\n\n\t$inspect(count).with((type, count) => {\n\t\tif (type === 'update') {\n\t\t\tdebugger; // or `console.trace`, or whatever you want\n\t\t}\n\t});\n</script>\n\n<button onclick={() => count++}>Increment</button>\n```\n\n<!-- codeblock:end -->\n\n## $inspect.trace(...)\n\nThis rune, added in 5.14, causes the surrounding function to be _traced_ in development. Any time the function re-runs as part of an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.\n\n```svelte\n<script>\n\timport { doSomeWork } from './elsewhere';\n\n\t$effect(() => {\n\t\t+++// $inspect.trace must be the first statement of a function body+++\n\t\t+++$inspect.trace();+++\n\t\tdoSomeWork();\n\t});\n</script>\n```\n\n`$inspect.trace` takes an optional first argument which will be used as the label.\n"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "svelte-core-bestpractices/references/render.md",
|
|
147
|
+
"include": [
|
|
148
|
+
"skills"
|
|
149
|
+
],
|
|
150
|
+
"exclude": [],
|
|
151
|
+
"contents": "To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.\n\n```svelte\n{#snippet sum(a, b)}\n\t<p>{a} + {b} = {a + b}</p>\n{/snippet}\n\n{@render sum(1, 2)}\n{@render sum(3, 4)}\n{@render sum(5, 6)}\n```\n\nThe expression can be an identifier like `sum`, or an arbitrary JavaScript expression:\n\n```svelte\n{@render (cool ? coolSnippet : lameSnippet)()}\n```\n\n## Optional snippets\n\nIf the snippet is potentially undefined — for example, because it's an incoming prop — then you can use optional chaining to only render it when it _is_ defined:\n\n```svelte\n{@render children?.()}\n```\n\nAlternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:\n\n```svelte\n{#if children}\n\t{@render children()}\n{:else}\n\t<p>fallback content</p>\n{/if}\n```\n"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "svelte-core-bestpractices/references/snippet.md",
|
|
155
|
+
"include": [
|
|
156
|
+
"skills"
|
|
157
|
+
],
|
|
158
|
+
"exclude": [],
|
|
159
|
+
"contents": "```svelte\n<!--- copy: false --->\n{#snippet name()}...{/snippet}\n```\n\n```svelte\n<!--- copy: false --->\n{#snippet name(param1, param2, paramN)}...{/snippet}\n```\n\nSnippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...\n\n```svelte\n{#each images as image}\n\t{#if image.href}\n\t\t<a href={image.href}>\n\t\t\t<figure>\n\t\t\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t\t\t<figcaption>{image.caption}</figcaption>\n\t\t\t</figure>\n\t\t</a>\n\t{:else}\n\t\t<figure>\n\t\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t\t<figcaption>{image.caption}</figcaption>\n\t\t</figure>\n\t{/if}\n{/each}\n```\n\n...you can write this:\n\n```svelte\n{#snippet figure(image)}\n\t<figure>\n\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t<figcaption>{image.caption}</figcaption>\n\t</figure>\n{/snippet}\n\n{#each images as image}\n\t{#if image.href}\n\t\t<a href={image.href}>\n\t\t\t{@render figure(image)}\n\t\t</a>\n\t{:else}\n\t\t{@render figure(image)}\n\t{/if}\n{/each}\n```\n\nLike function declarations, snippets can have an arbitrary number of parameters, which can have default values, and you can destructure each parameter. You cannot use rest parameters, however.\n\n## Snippet scope\n\nSnippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the `<script>` tag or in `{#each ...}` blocks...\n\n<!-- codeblock:start {\"title\":\"Snippets\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet { message = `it's great to see you!` } = $props();\n</script>\n\n{#snippet hello(name)}\n\t<p>hello {name}! {message}!</p>\n{/snippet}\n\n{@render hello('alice')}\n{@render hello('bob')}\n```\n\n<!-- codeblock:end -->\n\n...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):\n\n```svelte\n<div>\n\t{#snippet x()}\n\t\t{#snippet y()}...{/snippet}\n\n\t\t<!-- this is fine -->\n\t\t{@render y()}\n\t{/snippet}\n\n\t<!-- this will error, as `y` is not in scope -->\n\t{@render y()}\n</div>\n\n<!-- this will also error, as `x` is not in scope -->\n{@render x()}\n```\n\nSnippets can reference themselves and each other:\n\n<!-- codeblock:start {\"title\":\"Self-referencing snippets\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n{#snippet blastoff()}\n\t<span>🚀</span>\n{/snippet}\n\n{#snippet countdown(n)}\n\t{#if n > 0}\n\t\t<span>{n}...</span>\n\t\t{@render countdown(n - 1)}\n\t{:else}\n\t\t{@render blastoff()}\n\t{/if}\n{/snippet}\n\n{@render countdown(10)}\n```\n\n<!-- codeblock:end -->\n\n## Passing snippets to components\n\n### Explicit props\n\nWithin the template, snippets are values just like any other. As such, they can be passed to components as props:\n\n<!-- codeblock:start {\"title\":\"Explicit snippet props\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Table from './Table.svelte';\n\n\tconst fruits = [\n\t\t{ name: 'apples', qty: 5, price: 2 },\n\t\t{ name: 'bananas', qty: 10, price: 1 },\n\t\t{ name: 'cherries', qty: 20, price: 0.5 }\n\t];\n</script>\n\n{#snippet header()}\n\t<th>fruit</th>\n\t<th>qty</th>\n\t<th>price</th>\n\t<th>total</th>\n{/snippet}\n\n{#snippet row(d)}\n\t<td>{d.name}</td>\n\t<td>{d.qty}</td>\n\t<td>{d.price}</td>\n\t<td>{d.qty * d.price}</td>\n{/snippet}\n\n<Table data={fruits} +++{header} {row}+++ />\n```\n\n```svelte\n<!--- file: Table.svelte --->\n<script>\n\tlet { data, header, row } = $props();\n</script>\n\n<table>\n\t{#if header}\n\t\t<thead>\n\t\t\t<tr>{@render header()}</tr>\n\t\t</thead>\n\t{/if}\n\n\t<tbody>\n\t\t{#each data as d}\n\t\t\t<tr>{@render row(d)}</tr>\n\t\t{/each}\n\t</tbody>\n</table>\n\n<style>\n\ttable {\n\t\ttext-align: left;\n\t\tborder-spacing: 0;\n\t}\n\n\ttbody tr:nth-child(2n+1) {\n\t\tbackground: ButtonFace;\n\t}\n\n\ttable :global(th), table :global(td) {\n\t\tpadding: 0.5em;\n\t}\n</style>\n```\n\n<!-- codeblock:end -->\n\nThink about it like passing content instead of data to a component. The concept is similar to slots in web components.\n\n### Implicit props\n\nAs an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:\n\n<!-- codeblock:start {\"title\":\"Implicit snippet props\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Table from './Table.svelte';\n\n\tconst fruits = [\n\t\t{ name: 'apples', qty: 5, price: 2 },\n\t\t{ name: 'bananas', qty: 10, price: 1 },\n\t\t{ name: 'cherries', qty: 20, price: 0.5 }\n\t];\n</script>\n\n<Table data={fruits}>\n\t{#snippet header()}\n\t\t<th>fruit</th>\n\t\t<th>qty</th>\n\t\t<th>price</th>\n\t\t<th>total</th>\n\t{/snippet}\n\n\t{#snippet row(d)}\n\t\t<td>{d.name}</td>\n\t\t<td>{d.qty}</td>\n\t\t<td>{d.price}</td>\n\t\t<td>{d.qty * d.price}</td>\n\t{/snippet}\n</Table>\n```\n\n```svelte\n<!--- file: Table.svelte --->\n<script>\n\tlet { data, header, row } = $props();\n</script>\n\n<table>\n\t{#if header}\n\t\t<thead>\n\t\t\t<tr>{@render header()}</tr>\n\t\t</thead>\n\t{/if}\n\n\t<tbody>\n\t\t{#each data as d}\n\t\t\t<tr>{@render row(d)}</tr>\n\t\t{/each}\n\t</tbody>\n</table>\n\n<style>\n\ttable {\n\t\ttext-align: left;\n\t\tborder-spacing: 0;\n\t}\n\n\ttbody tr:nth-child(2n+1) {\n\t\tbackground: ButtonFace;\n\t}\n\n\ttable :global(th), table :global(td) {\n\t\tpadding: 0.5em;\n\t}\n</style>\n```\n\n<!-- codeblock:end -->\n\n### Implicit `children` snippet\n\nAny content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:\n\n<!-- codeblock:start {\"title\":\"Implicit children snippet\",\"selected\":\"Button.svelte\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Button from './Button.svelte';\n</script>\n\n<Button>click me</Button>\n```\n\n```svelte\n<!--- file: Button.svelte --->\n<script>\n\tlet { children } = $props();\n</script>\n\n<!-- result will be <button>click me</button> -->\n<button>{@render children()}</button>\n```\n\n<!-- codeblock:end -->\n\n> [!NOTE] Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name\n\n### Optional snippet props\n\nYou can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set...\n\n```svelte\n<script>\n let { children } = $props();\n</script>\n\n{@render children?.()}\n```\n\n...or use an `#if` block to render fallback content:\n\n```svelte\n<script>\n let { children } = $props();\n</script>\n\n{#if children}\n {@render children()}\n{:else}\n fallback content\n{/if}\n```\n\n## Typing snippets\n\nSnippets implement the `Snippet` interface imported from `'svelte'`:\n\n```svelte\n<script lang=\"ts\">\n\timport type { Snippet } from 'svelte';\n\n\tinterface Props {\n\t\tdata: any[];\n\t\tchildren: Snippet;\n\t\trow: Snippet<[any]>;\n\t}\n\n\tlet { data, children, row }: Props = $props();\n</script>\n```\n\nWith this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters.\n\nWe can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type:\n\n```svelte\n<script lang=\"ts\" generics=\"T\">\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tdata,\n\t\tchildren,\n\t\trow\n\t}: {\n\t\tdata: T[];\n\t\tchildren: Snippet;\n\t\trow: Snippet<[T]>;\n\t} = $props();\n</script>\n```\n\n## Exporting snippets\n\nSnippets declared at the top level of a `.svelte` file can be exported from a `<script module>` for use in other components, provided they don't reference any declarations in a non-module `<script>` (whether directly or indirectly, via other snippets):\n\n<!-- codeblock:start {\"title\":\"Exported snippets\",\"selected\":\"snippets.svelte\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport { add } from './snippets.svelte';\n</script>\n\n{@render add(1, 2)}\n\n```\n\n```svelte\n<!--- file: snippets.svelte --->\n<script module>\n\texport { add };\n</script>\n\n{#snippet add(a, b)}\n\t{a} + {b} = {a + b}\n{/snippet}\n```\n\n<!-- codeblock:end -->\n\n> [!NOTE]\n> This requires Svelte 5.5.0 or newer\n\n## Programmatic snippets\n\nSnippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.\n\n## Snippets and slots\n\nIn Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.\n"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "svelte-core-bestpractices/references/svelte-reactivity.md",
|
|
163
|
+
"include": [
|
|
164
|
+
"skills"
|
|
165
|
+
],
|
|
166
|
+
"exclude": [],
|
|
167
|
+
"contents": "## createSubscriber\n\n<blockquote class=\"since note\">\n\nAvailable since 5.7.0\n\n</blockquote>\n\nReturns a `subscribe` function that integrates external event-based systems with Svelte's reactivity.\nIt's particularly useful for integrating with web APIs like `MediaQuery`, `IntersectionObserver`, or `WebSocket`.\n\nIf `subscribe` is called inside an effect (including indirectly, for example inside a getter),\nthe `start` callback will be called with an `update` function. Whenever `update` is called, the effect re-runs.\n\nIf `start` returns a cleanup function, it will be called when the effect is destroyed.\n\nIf `subscribe` is called in multiple effects, `start` will only be called once as long as the effects\nare active, and the returned teardown function will only be called when all effects are destroyed.\n\nIt's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):\n\n```js\n// @errors: 7031\nimport { createSubscriber } from 'svelte/reactivity';\nimport { on } from 'svelte/events';\n\nexport class MediaQuery {\n\t#query;\n\t#subscribe;\n\n\tconstructor(query) {\n\t\tthis.#query = window.matchMedia(`(${query})`);\n\n\t\tthis.#subscribe = createSubscriber((update) => {\n\t\t\t// when the `change` event occurs, re-run any effects that read `this.current`\n\t\t\tconst off = on(this.#query, 'change', update);\n\n\t\t\t// stop listening when all the effects are destroyed\n\t\t\treturn () => off();\n\t\t});\n\t}\n\n\tget current() {\n\t\t// This makes the getter reactive, if read in an effect\n\t\tthis.#subscribe();\n\n\t\t// Return the current state of the query, whether or not we're in an effect\n\t\treturn this.#query.matches;\n\t}\n}\n```\n\n<div class=\"ts-block\">\n\n```dts\nfunction createSubscriber(\n\tstart: (update: () => void) => (() => void) | void\n): () => void;\n```\n\n</div>\n"
|
|
168
|
+
},
|
|
73
169
|
{
|
|
74
170
|
"name": "package.json",
|
|
75
171
|
"include": [
|
package/dist/src/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as StringQuestion, C as BooleanQuestion, D as OptionValues, E as OptionDefinition, O as Question, S as BaseQuestion, T as NumberQuestion, _ as SvApi, a as Addon, b as Workspace, c as AddonReference, d as ConditionDefinition, f as ConfiguredAddon, g as SetupResult, h as PreparedAddon, i as add, j as officialAddons, k as SelectQuestion, l as AddonResult, m as OptionBuilder, n as InstallOptions, o as AddonDefinition, p as LoadedAddon, r as OptionMap, s as AddonInput, t as AddonMap, u as AddonSource, v as defineAddon, w as MultiSelectQuestion, x as WorkspaceOptions, y as defineAddonOptions } from "../engine-
|
|
1
|
+
import { A as StringQuestion, C as BooleanQuestion, D as OptionValues, E as OptionDefinition, O as Question, S as BaseQuestion, T as NumberQuestion, _ as SvApi, a as Addon, b as Workspace, c as AddonReference, d as ConditionDefinition, f as ConfiguredAddon, g as SetupResult, h as PreparedAddon, i as add, j as officialAddons, k as SelectQuestion, l as AddonResult, m as OptionBuilder, n as InstallOptions, o as AddonDefinition, p as LoadedAddon, r as OptionMap, s as AddonInput, t as AddonMap, u as AddonSource, v as defineAddon, w as MultiSelectQuestion, x as WorkspaceOptions, y as defineAddonOptions } from "../engine-5Sg3lJiQ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/create/index.d.ts
|
|
4
4
|
type TemplateType = (typeof templateTypes)[number];
|
package/dist/src/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as defineAddonOptions, m as create$1, o as officialAddons, s as defineAddon, t as add, y as svDeprecated } from "../engine-
|
|
1
|
+
import { c as defineAddonOptions, m as create$1, o as officialAddons, s as defineAddon, t as add, y as svDeprecated } from "../engine-B7chsCPG.mjs";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
function create(cwdOrOptions, legacyOptions) {
|
|
4
4
|
if (typeof cwdOrOptions === "string") {
|
package/dist/src/testing.d.mts
CHANGED
package/dist/src/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as __toESM, b as __commonJSMin, f as R, i as addPnpmAllowBuilds, m as create, p as z, t as add, x as __require } from "../engine-
|
|
1
|
+
import { S as __toESM, b as __commonJSMin, f as R, i as addPnpmAllowBuilds, m as create, p as z, t as add, x as __require } from "../engine-B7chsCPG.mjs";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import V from "node:process";
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"sv": "^0.
|
|
25
|
+
"sv": "^0.17.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"sv": "^0.
|
|
29
|
-
"@sveltejs/sv-utils": "^0.3.
|
|
28
|
+
"sv": "^0.17.0",
|
|
29
|
+
"@sveltejs/sv-utils": "^0.3.3",
|
|
30
30
|
"@playwright/test": "^1.60.0",
|
|
31
31
|
"@types/node": "^25.9.1",
|
|
32
32
|
"tsdown": "^0.22.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A command line interface (CLI) for creating and maintaining Svelte applications",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@sveltejs/sv-utils": "0.3.
|
|
28
|
+
"@sveltejs/sv-utils": "0.3.3"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.5.0",
|