Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .changeset/coupling-sv-and-sv-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down
22 changes: 20 additions & 2 deletions packages/sv-utils/src/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -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;
Expand Down
6 changes: 2 additions & 4 deletions packages/sv/src/cli/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/sv/src/core/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export function updateAgent(
}
}

export const commonFilePaths = {
export const filePaths = {
packageJson: 'package.json',
svelteConfig: 'svelte.config.js',
svelteConfigTS: 'svelte.config.ts',
Expand Down
6 changes: 3 additions & 3 deletions packages/sv/src/core/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Addons extends AddonMap> = {
Expand Down
42 changes: 20 additions & 22 deletions packages/sv/src/core/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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<string, string> = {};
if (override?.dependencies) {
Expand All @@ -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,
Expand Down Expand Up @@ -156,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;
},
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions packages/sv/src/create/index.ts
Original file line number Diff line number Diff line change
@@ -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];
Expand Down Expand Up @@ -81,7 +81,7 @@ function write_template_files(template: string, types: LanguageType, name: strin
function write_common_files(cwd: string, options: Omit<Options, 'cwd'>, 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) => {
Expand All @@ -90,7 +90,7 @@ function write_common_files(cwd: string, options: Omit<Options, 'cwd'>, 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 {
Expand Down
6 changes: 3 additions & 3 deletions packages/sv/src/create/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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({}) });
Expand Down
2 changes: 1 addition & 1 deletion packages/sv/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreateOptions, 'cwd'>): void;
export function create(options: CreateOptions): void;
export function create(
Expand Down
Loading