From 868fa4bce7ba485ebaa1a03f633317a6dc089843 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 6 Aug 2026 00:11:43 +0800 Subject: [PATCH 1/7] only error if it is standalone addon --- documentation/docs/30-add-ons/99-community.md | 4 ++-- packages/sv/src/core/fetch-packages.ts | 19 +++++++++++++++---- .../src/create/shared/+addon/CONTRIBUTING.md | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/documentation/docs/30-add-ons/99-community.md b/documentation/docs/30-add-ons/99-community.md index 9af218bd5..35bef18b6 100644 --- a/documentation/docs/30-add-ons/99-community.md +++ b/documentation/docs/30-add-ons/99-community.md @@ -150,7 +150,7 @@ Community add-ons are bundled with [tsdown](https://tsdown.dev/) into a single f ### `package.json` -Your add-on must have `sv` as a peer dependency and **no** `dependencies` in `package.json`: +Your add-on must have `sv` as a peer dependency. Any `dependencies` declared will **not** be available at runtime, everything must be bundled: ```jsonc { @@ -164,7 +164,7 @@ Your add-on must have `sv` as a peer dependency and **no** `dependencies` in `pa "publishConfig": { "access": "public" }, - // cannot have dependencies + // packages declared here will not be available during runtime, it must be bundled "dependencies": {}, "peerDependencies": { // minimum version required to run by this add-on diff --git a/packages/sv/src/core/fetch-packages.ts b/packages/sv/src/core/fetch-packages.ts index e00db1d52..4f4edcf45 100644 --- a/packages/sv/src/core/fetch-packages.ts +++ b/packages/sv/src/core/fetch-packages.ts @@ -1,4 +1,4 @@ -import { color, coerceVersion, downloadJson } from '@sveltejs/sv-utils'; +import { color, coerceVersion, dedent, downloadJson } from '@sveltejs/sv-utils'; import { unpackTar } from 'modern-tar/fs'; import fs from 'node:fs'; import { platform } from 'node:os'; @@ -13,9 +13,18 @@ import type { AddonDefinition, AddonReference } from './config.ts'; // path to the `node_modules` directory of `sv` const NODE_MODULES = fileURLToPath(new URL('../../node_modules', import.meta.url)); +function isStandalone(addonPkg: Record): boolean { + const exports = addonPkg.exports; + if (!exports) return true; + if (typeof exports === 'string') return true; + if (typeof exports === 'object') return Object.keys(exports).length === 1; + return true; +} + function verifyPackage(addonPkg: Record, specifier: string): string | undefined { const peerDeps = { ...addonPkg.peerDependencies }; const deps = { ...addonPkg.dependencies }; + const standalone = isStandalone(addonPkg); // valid addons should always have `sv` as a peerDependency const addonSvVersion = peerDeps['sv']; @@ -25,10 +34,12 @@ function verifyPackage(addonPkg: Record, specifier: string): string ); } - // addons should not have any dependencies (everything should be bundled) - if (Object.keys(deps).length > 0) { + // standalone addons must not have dependencies (everything should be bundled) + if (standalone && Object.keys(deps).length > 0) { throw new Error( - `Invalid add-on package detected: '${specifier}'\nCommunity add-ons should not have any 'dependencies'. Use 'peerDependencies' for 'sv' and bundle everything else` + dedent` + Invalid add-on package detected: '${specifier}' + Standalone add-ons must not have 'dependencies' in package.json. Everything should be bundled with your add-on.` ); } diff --git a/packages/sv/src/create/shared/+addon/CONTRIBUTING.md b/packages/sv/src/create/shared/+addon/CONTRIBUTING.md index d5da26c73..dd520ac7c 100644 --- a/packages/sv/src/create/shared/+addon/CONTRIBUTING.md +++ b/packages/sv/src/create/shared/+addon/CONTRIBUTING.md @@ -45,4 +45,4 @@ npm publish ## Things to be aware of -Community add-ons must have `sv` as a `peerDependency` and should **not** have any `dependencies`. Everything else (including `@sveltejs/sv-utils`) is bundled at build time by tsdown. +Community add-ons must have `sv` as a `peerDependency`. Any `dependencies` declared in `package.json` will not be available at runtime. Everything else (including `@sveltejs/sv-utils`) is bundled at build time by tsdown. From 1bc543f593d8e32235453ef46e0236b238c2b2b0 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 6 Aug 2026 00:15:34 +0800 Subject: [PATCH 2/7] changeset --- .changeset/warm-snails-hope.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/warm-snails-hope.md diff --git a/.changeset/warm-snails-hope.md b/.changeset/warm-snails-hope.md new file mode 100644 index 000000000..1ae5cf131 --- /dev/null +++ b/.changeset/warm-snails-hope.md @@ -0,0 +1,5 @@ +--- +"sv": patch +--- + +fix(addon): Dependencies are allowed in multi-export repos. From 9ea536c97fcdcebd0e147ad09439ec02f5d41002 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:35:08 +0000 Subject: [PATCH 3/7] tweak error handling to address non-bundled deps --- packages/sv/src/core/fetch-packages.ts | 85 ++++++++++++++------------ 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/packages/sv/src/core/fetch-packages.ts b/packages/sv/src/core/fetch-packages.ts index 4f4edcf45..c965d2dfe 100644 --- a/packages/sv/src/core/fetch-packages.ts +++ b/packages/sv/src/core/fetch-packages.ts @@ -1,4 +1,4 @@ -import { color, coerceVersion, dedent, downloadJson } from '@sveltejs/sv-utils'; +import { color, coerceVersion, downloadJson } from '@sveltejs/sv-utils'; import { unpackTar } from 'modern-tar/fs'; import fs from 'node:fs'; import { platform } from 'node:os'; @@ -13,18 +13,8 @@ import type { AddonDefinition, AddonReference } from './config.ts'; // path to the `node_modules` directory of `sv` const NODE_MODULES = fileURLToPath(new URL('../../node_modules', import.meta.url)); -function isStandalone(addonPkg: Record): boolean { - const exports = addonPkg.exports; - if (!exports) return true; - if (typeof exports === 'string') return true; - if (typeof exports === 'object') return Object.keys(exports).length === 1; - return true; -} - function verifyPackage(addonPkg: Record, specifier: string): string | undefined { const peerDeps = { ...addonPkg.peerDependencies }; - const deps = { ...addonPkg.dependencies }; - const standalone = isStandalone(addonPkg); // valid addons should always have `sv` as a peerDependency const addonSvVersion = peerDeps['sv']; @@ -34,15 +24,6 @@ function verifyPackage(addonPkg: Record, specifier: string): string ); } - // standalone addons must not have dependencies (everything should be bundled) - if (standalone && Object.keys(deps).length > 0) { - throw new Error( - dedent` - Invalid add-on package detected: '${specifier}' - Standalone add-ons must not have 'dependencies' in package.json. Everything should be bundled with your add-on.` - ); - } - // Check version compatibility and warn if there's a major version mismatch const addon = coerceVersion(addonSvVersion); const sv_major = coerceVersion(pkg.version).major; @@ -123,7 +104,7 @@ export async function downloadPackage(options: DownloadOptions): Promise { +async function importAddonCode( + pkgName: string, + pkgVersion: string, + exports?: Record +): Promise { const issues: string[] = []; - let details: AddonDefinition | undefined; - try { - ({ default: details } = await import(`${pkgName}/sv`)); - } catch { - issues.push(`'/sv' export not found`); + const error = () => { + return new Error( + `Failed to load add-on '${pkgName}@${pkgVersion}':\n- ${issues.join('\n- ')}\n\n` + + `Please report this to the add-on author.` + ); + }; + + if (!exports) { + issues.push(`'exports' field not found in package.json`); + throw error(); } - if (!details) { + const svImport = exports['./sv'] ? `${pkgName}/sv` : undefined; + const defaultImport = exports['.'] ? pkgName : undefined; + if (!svImport && !defaultImport) { + issues.push(`export conditions './sv' or '.' are not present in package.json`); + throw error(); + } + + let details: AddonDefinition | undefined; + + for (const importPath of [svImport, defaultImport]) { + if (!importPath) continue; try { - ({ default: details } = await import(pkgName)); - } catch { - issues.push(`default export not found`); + details ??= await import(importPath).then((m) => m.default); + } catch (e) { + if (isNodeError(e)) { + if (e.code === 'ERR_MODULE_NOT_FOUND') { + issues.push('the add-on contains dependencies that are not bundled'); + throw error(); + } + issues.push(`Failed to import add-on '${importPath}': ${e.message}`); + } else { + issues.push(`An unknown error has occurred: ${e}`); + } } } - if (!details && issues.length > 0) { - throw new Error( - `Failed to load add-on '${pkgName}@${pkgVersion}':\n- ${issues.join('\n- ')}\n\n` + - `Please report this to the add-on author.` - ); + if (!details) { + throw error(); } - return details!; + return details; +} + +function isNodeError(e: unknown): e is Error & { code: string } { + return e instanceof Error && 'code' in e && typeof e.code === 'string'; } type PackageJSON = { From 05d97ff37c1e560acf391ddffe15f240c28686b7 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:35:26 +0000 Subject: [PATCH 4/7] fix colors for thrown errors --- packages/sv/src/core/common.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/sv/src/core/common.ts b/packages/sv/src/core/common.ts index e9403ea19..a2eea09b8 100644 --- a/packages/sv/src/core/common.ts +++ b/packages/sv/src/core/common.ts @@ -275,7 +275,14 @@ export function updateReadme(projectPath: string, command: string) { } export function errorAndExit(message: string) { - p.log.error(message); + const [firstLine, ...restLines] = message.split('\n'); + + p.log.error(firstLine); + // Fixes issue where the first line of the error message is not the same color as the rest of the lines + for (const line of restLines) { + p.log.message(color.optional(line), { spacing: 0 }); + } + p.log.message(); p.cancel('Operation failed.'); process.exit(1); From b3a01534234e0b53b93a4ae95b8eb20dc3c5730f Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:45:59 +0000 Subject: [PATCH 5/7] changeset --- .changeset/warm-snails-hope.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/warm-snails-hope.md b/.changeset/warm-snails-hope.md index 1ae5cf131..c1d65a32c 100644 --- a/.changeset/warm-snails-hope.md +++ b/.changeset/warm-snails-hope.md @@ -1,5 +1,5 @@ --- -"sv": patch +'sv': patch --- -fix(addon): Dependencies are allowed in multi-export repos. +fix(addon): relax dependency fields restriction on community add-ons From 99ad4a5bfff5a8f22f6d06944a8012d77d55b6cc Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:56:13 +0000 Subject: [PATCH 6/7] update snapshot --- packages/sv/src/cli/tests/snapshots/@my-org/sv/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv/src/cli/tests/snapshots/@my-org/sv/CONTRIBUTING.md b/packages/sv/src/cli/tests/snapshots/@my-org/sv/CONTRIBUTING.md index d5da26c73..dd520ac7c 100644 --- a/packages/sv/src/cli/tests/snapshots/@my-org/sv/CONTRIBUTING.md +++ b/packages/sv/src/cli/tests/snapshots/@my-org/sv/CONTRIBUTING.md @@ -45,4 +45,4 @@ npm publish ## Things to be aware of -Community add-ons must have `sv` as a `peerDependency` and should **not** have any `dependencies`. Everything else (including `@sveltejs/sv-utils`) is bundled at build time by tsdown. +Community add-ons must have `sv` as a `peerDependency`. Any `dependencies` declared in `package.json` will not be available at runtime. Everything else (including `@sveltejs/sv-utils`) is bundled at build time by tsdown. From 953e2dac2189c8f44cc732d452451d33c8f86867 Mon Sep 17 00:00:00 2001 From: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:03:44 +0000 Subject: [PATCH 7/7] tweak type for isNodeError --- packages/sv/src/core/fetch-packages.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/sv/src/core/fetch-packages.ts b/packages/sv/src/core/fetch-packages.ts index c965d2dfe..1681e1112 100644 --- a/packages/sv/src/core/fetch-packages.ts +++ b/packages/sv/src/core/fetch-packages.ts @@ -94,7 +94,8 @@ export async function downloadPackage(options: DownloadOptions): Promise