diff --git a/.changeset/warm-snails-hope.md b/.changeset/warm-snails-hope.md new file mode 100644 index 000000000..c1d65a32c --- /dev/null +++ b/.changeset/warm-snails-hope.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +fix(addon): relax dependency fields restriction on community add-ons 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/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. 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); diff --git a/packages/sv/src/core/fetch-packages.ts b/packages/sv/src/core/fetch-packages.ts index e00db1d52..1681e1112 100644 --- a/packages/sv/src/core/fetch-packages.ts +++ b/packages/sv/src/core/fetch-packages.ts @@ -15,7 +15,6 @@ const NODE_MODULES = fileURLToPath(new URL('../../node_modules', import.meta.url function verifyPackage(addonPkg: Record, specifier: string): string | undefined { const peerDeps = { ...addonPkg.peerDependencies }; - const deps = { ...addonPkg.dependencies }; // valid addons should always have `sv` as a peerDependency const addonSvVersion = peerDeps['sv']; @@ -25,13 +24,6 @@ function verifyPackage(addonPkg: Record, specifier: string): string ); } - // addons should not have any dependencies (everything should be bundled) - if (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` - ); - } - // Check version compatibility and warn if there's a major version mismatch const addon = coerceVersion(addonSvVersion); const sv_major = coerceVersion(pkg.version).major; @@ -102,7 +94,8 @@ 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(err: unknown): err is Error & NodeJS.ErrnoException { + return err instanceof Error; } type PackageJSON = { 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.