From 3e7d891d0d604e7fc4604ec92c64b0480b622f74 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 01:14:31 +0800 Subject: [PATCH 1/6] update readme --- .changeset/coupling-sv-and-sv-utils.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/coupling-sv-and-sv-utils.md b/.changeset/coupling-sv-and-sv-utils.md index 60630d8a9..2da746c6d 100644 --- a/.changeset/coupling-sv-and-sv-utils.md +++ b/.changeset/coupling-sv-and-sv-utils.md @@ -14,10 +14,10 @@ feat: decouple sv / sv-utils, explicit public API, deprecation pass **`sv`** -- `create()` signature changed to `create({ cwd, ...options })`. The old `create(cwd, options)` form still works but is deprecated and will be removed in the next major. -- `sv.pnpmBuildDependency()` is deprecated. Use `sv.file()` with `pnpm.onlyBuiltDependencies()` from `@sveltejs/sv-utils` instead. Still works for now. -- `workspace.file.prettierignore`, `.prettierrc`, `.eslintConfig`, `.vscodeSettings`, `.vscodeExtensions` are deprecated. Use the raw strings directly (e.g. `'.prettierignore'`). Still works for now. +- `create()` signature changed to `create({ cwd, ...options })`. The old `create(cwd, options)` is deprecated and will be removed in the next major release. +- `sv.pnpmBuildDependency()` is deprecated and will be removed in the next major release. Use `sv.file()` with `pnpm.onlyBuiltDependencies()` from `@sveltejs/sv-utils` instead. +- `workspace.file.prettierignore`, `.prettierrc`, `.eslintConfig`, `.vscodeSettings`, `.vscodeExtensions` are deprecated and will be removed in the next major release. Use the raw strings directly (e.g. `'.prettierignore'`). - Add `workspace.file.findUp()` to locate files by walking up the directory tree. -- Make type exports explicit (no more `export type *`). Removed types that were never part of the intended public API: `PackageDefinition`, `Scripts`, `TestDefinition`. -- Remove `setup`, `createProject`, `startPreview`, `addPnpmBuildDependencies` from `sv/testing` exports. - Add `api-surface.md` snapshots (auto-generated on build) to track the public API of `sv` and `@sveltejs/sv-utils`. +- Remove `setup`, `createProject`, `startPreview`, `addPnpmBuildDependencies` from `sv/testing` exports. +- Make type exports explicit (no more `export type *`). Removed types that were never part of the intended public API: `PackageDefinition`, `Scripts`, `TestDefinition`. From c07ac5794472f35f124e207e020b12d5b44099c1 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 01:20:55 +0800 Subject: [PATCH 2/6] rename `commonFilePaths` to `filePaths` --- packages/sv/src/cli/create.ts | 6 ++---- packages/sv/src/core/common.ts | 2 +- packages/sv/src/core/engine.ts | 6 +++--- packages/sv/src/core/workspace.ts | 22 ++++++++++------------ packages/sv/src/create/index.ts | 6 +++--- packages/sv/src/create/playground.ts | 6 +++--- 6 files changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index 75888d8ee..5f28b9aa5 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -480,10 +480,8 @@ export async function createVirtualWorkspace({ file: { ...tentativeWorkspace.file, viteConfig: - type === 'typescript' - ? common.commonFilePaths.viteConfigTS - : common.commonFilePaths.viteConfig, - svelteConfig: common.commonFilePaths.svelteConfig // currently we always use js files, never typescript files + type === 'typescript' ? common.filePaths.viteConfigTS : common.filePaths.viteConfig, + svelteConfig: common.filePaths.svelteConfig // currently we always use js files, never typescript files } }; diff --git a/packages/sv/src/core/common.ts b/packages/sv/src/core/common.ts index c834a08a7..dce24eab7 100644 --- a/packages/sv/src/core/common.ts +++ b/packages/sv/src/core/common.ts @@ -325,7 +325,7 @@ export function updateAgent( } } -export const commonFilePaths = { +export const filePaths = { packageJson: 'package.json', svelteConfig: 'svelte.config.js', svelteConfigTS: 'svelte.config.ts', diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index 99304e745..f575d92af 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -10,7 +10,7 @@ import { } from '@sveltejs/sv-utils'; import { NonZeroExitError, exec } from 'tinyexec'; import { createLoadedAddon } from '../cli/add.ts'; -import { commonFilePaths } from './common.ts'; +import { filePaths } from './common.ts'; import { getErrorHint, type Addon, @@ -53,8 +53,8 @@ function updatePackages( if (data.devDependencies) data.devDependencies = alphabetizePackageJsonDependencies(data.devDependencies); - saveFile(cwd, commonFilePaths.packageJson, generateCode()); - return commonFilePaths.packageJson; + saveFile(cwd, filePaths.packageJson, generateCode()); + return filePaths.packageJson; } export type InstallOptions = { diff --git a/packages/sv/src/core/workspace.ts b/packages/sv/src/core/workspace.ts index 1c30d550d..846b67640 100644 --- a/packages/sv/src/core/workspace.ts +++ b/packages/sv/src/core/workspace.ts @@ -9,7 +9,7 @@ import { import * as find from 'empathic/find'; import fs from 'node:fs'; import path from 'node:path'; -import { commonFilePaths } from './common.ts'; +import { filePaths } from './common.ts'; import { svDeprecated } from './deprecated.ts'; import type { OptionDefinition, OptionValues } from './options.ts'; import { detectPackageManager } from './package-manager.ts'; @@ -87,18 +87,16 @@ export async function createWorkspace({ const resolvedCwd = path.resolve(cwd); // Will go up and prioritize jsconfig.json as it's first in the array - const typeConfigOptions = [commonFilePaths.jsconfig, commonFilePaths.tsconfig]; + const typeConfigOptions = [filePaths.jsconfig, filePaths.tsconfig]; const typeConfig = find.any(typeConfigOptions, { cwd }) as Workspace['file']['typeConfig']; - const typescript = typeConfig?.endsWith(commonFilePaths.tsconfig) ?? false; + const typescript = typeConfig?.endsWith(filePaths.tsconfig) ?? false; // This is not linked with typescript detection - const viteConfigPath = path.join(resolvedCwd, commonFilePaths.viteConfigTS); - const viteConfig = fs.existsSync(viteConfigPath) - ? commonFilePaths.viteConfigTS - : commonFilePaths.viteConfig; - const svelteConfigPath = path.join(resolvedCwd, commonFilePaths.svelteConfigTS); + const viteConfigPath = path.join(resolvedCwd, filePaths.viteConfigTS); + const viteConfig = fs.existsSync(viteConfigPath) ? filePaths.viteConfigTS : filePaths.viteConfig; + const svelteConfigPath = path.join(resolvedCwd, filePaths.svelteConfigTS); const svelteConfig = fs.existsSync(svelteConfigPath) - ? commonFilePaths.svelteConfigTS - : commonFilePaths.svelteConfig; + ? filePaths.svelteConfigTS + : filePaths.svelteConfig; let dependencies: Record = {}; if (override?.dependencies) { @@ -113,7 +111,7 @@ export async function createWorkspace({ // we are still in the workspace (including the workspace root) directory.length >= workspaceRoot.length ) { - if (fs.existsSync(path.join(directory, commonFilePaths.packageJson))) { + if (fs.existsSync(path.join(directory, filePaths.packageJson))) { const { data: packageJson } = loadPackageJson(directory); dependencies = { ...packageJson.devDependencies, @@ -217,7 +215,7 @@ function findWorkspaceRoot(cwd: string): string { const { root } = path.parse(cwd); let directory = cwd; while (directory && directory !== root) { - if (fs.existsSync(path.join(directory, commonFilePaths.packageJson))) { + if (fs.existsSync(path.join(directory, filePaths.packageJson))) { // in pnpm it can be a file if (fs.existsSync(path.join(directory, 'pnpm-workspace.yaml'))) { return directory; diff --git a/packages/sv/src/create/index.ts b/packages/sv/src/create/index.ts index ffe49c5ee..8b487a481 100644 --- a/packages/sv/src/create/index.ts +++ b/packages/sv/src/create/index.ts @@ -1,7 +1,7 @@ import { sanitizeName } from '@sveltejs/sv-utils'; import fs from 'node:fs'; import path from 'node:path'; -import { commonFilePaths } from '../core/common.ts'; +import { filePaths } from '../core/common.ts'; import { mkdirp, copy, dist, getSharedFiles, replace, kv } from './utils.ts'; export type TemplateType = (typeof templateTypes)[number]; @@ -81,7 +81,7 @@ function write_template_files(template: string, types: LanguageType, name: strin function write_common_files(cwd: string, options: Omit, name: string) { const files = getSharedFiles(); - const pkg_file = path.join(cwd, commonFilePaths.packageJson); + const pkg_file = path.join(cwd, filePaths.packageJson); const pkg = /** @type {any} */ JSON.parse(fs.readFileSync(pkg_file, 'utf-8')); sort_files(files).forEach((file) => { @@ -90,7 +90,7 @@ function write_common_files(cwd: string, options: Omit, name: st if (exclude || !include) return; - if (file.name === commonFilePaths.packageJson) { + if (file.name === filePaths.packageJson) { const new_pkg = JSON.parse(file.contents); merge(pkg, new_pkg); } else { diff --git a/packages/sv/src/create/playground.ts b/packages/sv/src/create/playground.ts index f93788126..b93a393bb 100644 --- a/packages/sv/src/create/playground.ts +++ b/packages/sv/src/create/playground.ts @@ -9,7 +9,7 @@ import { } from '@sveltejs/sv-utils'; import fs from 'node:fs'; import path from 'node:path'; -import { commonFilePaths } from '../core/common.ts'; +import { filePaths } from '../core/common.ts'; import { getSharedFiles } from './utils.ts'; export function validatePlaygroundUrl(link: string): boolean { @@ -241,7 +241,7 @@ export function setupPlaygroundProject( fs.writeFileSync(filePath, newContent, 'utf-8'); // add packages as dependencies to package.json if requested - const pkgPath = path.join(cwd, commonFilePaths.packageJson); + const pkgPath = path.join(cwd, filePaths.packageJson); const pkgSource = fs.readFileSync(pkgPath, 'utf-8'); const pkgJson = parse.json(pkgSource); let updatePackageJson = false; @@ -255,7 +255,7 @@ export function setupPlaygroundProject( let experimentalAsyncNeeded = true; const addExperimentalAsync = () => { - const svelteConfigPath = path.join(cwd, commonFilePaths.svelteConfig); + const svelteConfigPath = path.join(cwd, filePaths.svelteConfig); const svelteConfig = fs.readFileSync(svelteConfigPath, 'utf-8'); const { ast, generateCode } = parse.script(svelteConfig); const { value: config } = js.exports.createDefault(ast, { fallback: js.object.create({}) }); From ac27535f179b8acf604b475c929b713badf9ac5e Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 01:30:52 +0800 Subject: [PATCH 3/6] update deprecated warning --- packages/sv/src/core/workspace.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/sv/src/core/workspace.ts b/packages/sv/src/core/workspace.ts index 846b67640..d7e22716e 100644 --- a/packages/sv/src/core/workspace.ts +++ b/packages/sv/src/core/workspace.ts @@ -38,15 +38,15 @@ export type Workspace = { package: 'package.json'; gitignore: '.gitignore'; - /** @deprecated use the string `'.prettierignore'` directly */ + /** @deprecated use the string `.prettierignore` instead. */ prettierignore: '.prettierignore'; - /** @deprecated use the string `'.prettierrc'` directly */ + /** @deprecated use the string `.prettierrc` instead. */ prettierrc: '.prettierrc'; - /** @deprecated use the string `'eslint.config.js'` directly */ + /** @deprecated use the string `eslint.config.js` instead. */ eslintConfig: 'eslint.config.js'; - /** @deprecated use the string `'.vscode/settings.json'` directly */ + /** @deprecated use the string `.vscode/settings.json` instead. */ vscodeSettings: '.vscode/settings.json'; - /** @deprecated use the string `'.vscode/extensions.json'` directly */ + /** @deprecated use the string `.vscode/extensions.json` instead. */ vscodeExtensions: '.vscode/extensions.json'; /** Get the relative path between two files */ @@ -154,35 +154,35 @@ export async function createWorkspace({ /** @deprecated */ get prettierignore() { svDeprecated( - '`workspace.file.prettierignore` is deprecated, use the string `".prettierignore"` directly' + '`workspace.file.prettierignore` is deprecated, use the string `.prettierignore` isntead.' ); return '.prettierignore' as const; }, /** @deprecated */ get prettierrc() { svDeprecated( - '`workspace.file.prettierrc` is deprecated, use the string `".prettierrc"` directly' + '`workspace.file.prettierrc` is deprecated, use the string `.prettierrc` isntead.' ); return '.prettierrc' as const; }, /** @deprecated */ get eslintConfig() { svDeprecated( - '`workspace.file.eslintConfig` is deprecated, use the string `"eslint.config.js"` directly' + '`workspace.file.eslintConfig` is deprecated, use the string `eslint.config.js` isntead.' ); return 'eslint.config.js' as const; }, /** @deprecated */ get vscodeSettings() { svDeprecated( - '`workspace.file.vscodeSettings` is deprecated, use the string `".vscode/settings.json"` directly' + '`workspace.file.vscodeSettings` is deprecated, use the string `.vscode/settings.json` isntead.' ); return '.vscode/settings.json' as const; }, /** @deprecated */ get vscodeExtensions() { svDeprecated( - '`workspace.file.vscodeExtensions` is deprecated, use the string `".vscode/extensions.json"` directly' + '`workspace.file.vscodeExtensions` is deprecated, use the string `.vscode/extensions.json` isntead.' ); return '.vscode/extensions.json' as const; }, From 3337f0380109ed4897f9b167c4e57035ca2580dc Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 01:43:32 +0800 Subject: [PATCH 4/6] nit comment --- packages/sv/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv/src/index.ts b/packages/sv/src/index.ts index ed4108934..7d99276e8 100644 --- a/packages/sv/src/index.ts +++ b/packages/sv/src/index.ts @@ -3,7 +3,7 @@ import { create as _create, type Options as CreateOptions } from './create/index export type { TemplateType, LanguageType } from './create/index.ts'; -/** @deprecated use `create({ cwd, ...options })` instead */ +/** @deprecated use `create({ cwd, ...options })` instead. */ export function create(cwd: string, options: Omit): void; export function create(options: CreateOptions): void; export function create( From 73c6808ac3a702d72eefcac0877fe895c2314096 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 02:11:45 +0800 Subject: [PATCH 5/6] add jsdocs for `files.ts` --- packages/sv-utils/src/files.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/sv-utils/src/files.ts b/packages/sv-utils/src/files.ts index 156e07271..c4c9c9135 100644 --- a/packages/sv-utils/src/files.ts +++ b/packages/sv-utils/src/files.ts @@ -14,12 +14,20 @@ export type Package = { }; /** Check if a file exists at the given workspace-relative path. */ +/** + * Checks the file. + * @param filePath - Resolves paths relative to the workspace. + */ export function fileExists(cwd: string, filePath: string): boolean { const fullFilePath = path.resolve(cwd, filePath); return fs.existsSync(fullFilePath); } -/** Synchronous load of a workspace-relative file; missing files yield `''`. */ +/** + * Reads the file. + * @param filePath - Resolves paths relative to the workspace. + * @returns The raw UTF-8 text, or `''` if the file is not found. + */ export function loadFile(cwd: string, filePath: string): string { const fullFilePath = path.resolve(cwd, filePath); @@ -32,7 +40,10 @@ export function loadFile(cwd: string, filePath: string): string { return text; } -/** Synchronous write of a workspace-relative file (creates parent dirs). */ +/** + * Writes the file. Will make parent directories as needed. + * @param filePath - Resolves paths relative to the workspace. + */ export function saveFile(cwd: string, filePath: string, content: string): void { const fullFilePath = path.resolve(cwd, filePath); const fullDirectoryPath = path.dirname(fullFilePath); @@ -47,6 +58,13 @@ export function saveFile(cwd: string, filePath: string, content: string): void { } /** Load and parse a workspace-relative `package.json`. Throws if missing or invalid. */ +/** + * Loads the workspace `package.json`. + * @returns + * - `source`: The raw UTF-8 text. + * - `data`: The parsed JSON object. + * - `generateCode`: A function to serialize the data back to a string. + */ export function loadPackageJson(cwd: string): { source: string; data: Package; From fe53995a45c71e74a128701b46016644a68632dd Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Tue, 7 Apr 2026 02:24:59 +0800 Subject: [PATCH 6/6] update `CONTRIBUTING.md` --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b9d4b874..37835e7c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,16 +153,16 @@ To run svelte-migrate locally: node ./packages/migrate/bin.js ``` -## Deprecation pattern +## Deprecation -When removing public API from `sv`, **do not hard-remove** it. Instead, deprecate it and keep it working until the next major version. +Public APIs cannot be changed in a minor release since it is a breaking change. Instead, the old behaviour is marked as deprecated until the next major version, at which point they can be removed. ### How to deprecate 1. **Add `@deprecated` JSDoc** on the type/function - IDEs will show strikethrough: ```ts - /** @deprecated use `newThing()` instead */ + /** @deprecated use `newThing()` instead. */ ``` 2. **Emit a runtime warning** (for functions/methods) using `svDeprecated()` from `core/deprecated.ts`. Warns once per message: @@ -175,7 +175,7 @@ When removing public API from `sv`, **do not hard-remove** it. Instead, deprecat ### Before a major release -Search for `svDeprecated` and `@deprecated` to find and remove all deprecated APIs: +Search for `svDeprecated` and `@deprecated` to find and remove all deprecated APIs. ## Generating changelogs