Skip to content
Open
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
892 changes: 38 additions & 854 deletions packages/sv-utils/api-surface.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/sv-utils/src/dedent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import dedentImpl from 'dedent';

/**
* Template-tag or single-string dedent helper (same behavior as the `dedent` package).
* Types are hand-written so the public `.d.mts` does not inline `dedent`'s full declarations.
*/
export type Dedent = {
(strings: TemplateStringsArray, ...values: unknown[]): string;
(source: string): string;
};

const dedent: Dedent = dedentImpl as Dedent;
export default dedent;
95 changes: 40 additions & 55 deletions packages/sv-utils/src/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ export type Package = {
workspaces?: string[];
};

export function getPackageJson(cwd: string): {
source: string;
data: Package;
generateCode: () => string;
} {
const packageText = readFile(cwd, commonFilePaths.packageJson);
if (!packageText) {
const pkgPath = path.join(cwd, commonFilePaths.packageJson);
throw new Error(`Invalid workspace: missing '${pkgPath}'`);
}
export const commonFilePaths = {
packageJson: 'package.json',
svelteConfig: 'svelte.config.js',
svelteConfigTS: 'svelte.config.ts',
jsconfig: 'jsconfig.json',
tsconfig: 'tsconfig.json',
viteConfig: 'vite.config.js',
viteConfigTS: 'vite.config.ts'
} as const;

const { data, generateCode } = parseJson(packageText);
return { source: packageText, data: data as Package, generateCode };
export function fileExists(cwd: string, filePath: string): boolean {
const fullFilePath = path.resolve(cwd, filePath);
return fs.existsSync(fullFilePath);
}

export function readFile(cwd: string, filePath: string): string {
/** Synchronous load of a workspace-relative file as UTF-8 text; missing files yield `''`. */
export function loadFile(cwd: string, filePath: string): string {
const fullFilePath = path.resolve(cwd, filePath);

if (!fileExists(cwd, filePath)) {
Expand All @@ -40,12 +41,8 @@ export function readFile(cwd: string, filePath: string): string {
return text;
}

export function fileExists(cwd: string, filePath: string): boolean {
const fullFilePath = path.resolve(cwd, filePath);
return fs.existsSync(fullFilePath);
}

export function writeFile(cwd: string, filePath: string, content: string): void {
/** Synchronous write of a workspace-relative file (creates parent dirs). */
export function saveFile(cwd: string, filePath: string, content: string): void {
const fullFilePath = path.resolve(cwd, filePath);
const fullDirectoryPath = path.dirname(fullFilePath);

Expand All @@ -58,44 +55,32 @@ export function writeFile(cwd: string, filePath: string, content: string): void
fs.writeFileSync(fullFilePath, content, 'utf8');
}

export function installPackages(
dependencies: Array<{ pkg: string; version: string; dev: boolean }>,
cwd: string
): string {
const { data, generateCode } = getPackageJson(cwd);

for (const dependency of dependencies) {
if (dependency.dev) {
data.devDependencies ??= {};
data.devDependencies[dependency.pkg] = dependency.version;
} else {
data.dependencies ??= {};
data.dependencies[dependency.pkg] = dependency.version;
}
export function loadPackageJson(cwd: string): {
source: string;
data: Package;
generateCode: () => string;
} {
const packageText = loadFile(cwd, commonFilePaths.packageJson);
if (!packageText) {
const pkgPath = path.join(cwd, commonFilePaths.packageJson);
throw new Error(`Invalid workspace: missing '${pkgPath}'`);
}

if (data.dependencies) data.dependencies = alphabetizeProperties(data.dependencies);
if (data.devDependencies) data.devDependencies = alphabetizeProperties(data.devDependencies);

writeFile(cwd, commonFilePaths.packageJson, generateCode());
return commonFilePaths.packageJson;
const { data, generateCode } = parseJson(packageText);
return { source: packageText, data: data as Package, generateCode };
}

function alphabetizeProperties(obj: Record<string, string>) {
const orderedObj: Record<string, string> = {};
const sortedEntries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
for (const [key, value] of sortedEntries) {
orderedObj[key] = value;
}
return orderedObj;
}
/**
* @deprecated Use {@link loadFile} instead. This alias will be removed in a future version.
*/
export const readFile: typeof loadFile = loadFile;

export const commonFilePaths = {
packageJson: 'package.json',
svelteConfig: 'svelte.config.js',
svelteConfigTS: 'svelte.config.ts',
jsconfig: 'jsconfig.json',
tsconfig: 'tsconfig.json',
viteConfig: 'vite.config.js',
viteConfigTS: 'vite.config.ts'
} as const;
/**
* @deprecated Use {@link saveFile} instead. This alias will be removed in a future version.
*/
export const writeFile: typeof saveFile = saveFile;

/**
* @deprecated Use {@link loadPackageJson} instead. This alias will be removed in a future version.
*/
export const getPackageJson: typeof loadPackageJson = loadPackageJson;
43 changes: 24 additions & 19 deletions packages/sv-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
resolveCommand as _resolveCommand,
type Agent,
type Command
} from 'package-manager-detector';
import {
parseCss,
parseHtml,
Expand All @@ -14,22 +9,19 @@ import {
} from './tooling/parsers.ts';

// External re-exports
export { default as dedent } from 'dedent';
export { default as dedent } from './dedent.ts';
export * as Walker from 'zimmerframe';

// Package managers (delegates to `package-manager-detector`; see `pm.ts`)
export {
AGENTS,
type AgentName,
COMMANDS,
constructCommand,
detect,
resolveCommand
} from 'package-manager-detector';

/** Resolves a package manager command and returns it as a string array (command + args). */
export function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[] {
const cmd = _resolveCommand(agent, command, args)!;
return [cmd.command, ...cmd.args];
}
resolveCommand,
resolveCommandArray
} from './pm.ts';

// Parsing & language namespaces
export * as css from './tooling/css/index.ts';
Expand Down Expand Up @@ -80,20 +72,33 @@ export { createPrinter } from './utils.ts';
export { sanitizeName } from './sanitize.ts';
export { downloadJson } from './downloadJson.ts';

// File system helpers
// File system helpers (sync, workspace-relative paths)
export {
commonFilePaths,
fileExists,
getPackageJson,
installPackages,
readFile,
writeFile,
loadFile,
loadPackageJson,
saveFile,
type Package
} from './files.ts';

/**
* @deprecated Use {@link loadFile} instead. This alias will be removed in a future version.
*/
export { readFile } from './files.ts';
/**
* @deprecated Use {@link saveFile} instead. This alias will be removed in a future version.
*/
export { writeFile } from './files.ts';
/**
* @deprecated Use {@link loadPackageJson} instead. This alias will be removed in a future version.
*/
export { getPackageJson } from './files.ts';

// Terminal styling
export { color } from './color.ts';

// Types
export type { Comments, AstTypes, SvelteAst } from './tooling/index.ts';
export type { TransformFn } from './tooling/transforms.ts';
export type { YamlDocument } from './tooling/parsers.ts';
24 changes: 24 additions & 0 deletions packages/sv-utils/src/pm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Thin wrapper around [`package-manager-detector`](https://github.com/antfu/package-manager-detector).
* Only the symbols re-exported from the package root are public — we keep this module small and
* avoid exposing the full upstream surface.
*/
import {
resolveCommand as _resolveCommand,
type Agent,
type Command
} from 'package-manager-detector';

export {
AGENTS,
type AgentName,
COMMANDS,
constructCommand,
detect,
resolveCommand
} from 'package-manager-detector';

export function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[] {
const cmd = _resolveCommand(agent, command, args)!;
return [cmd.command, ...cmd.args];
}
17 changes: 12 additions & 5 deletions packages/sv-utils/src/tooling/parsers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import type { TomlTable } from 'smol-toml';
import * as utils from './index.ts';

/**
* Minimal shape for YAML document roots from `parse.yaml` — avoids re-exporting the full `yaml` types.
* At runtime this is the library’s document type; only `get` / `set` are part of the public contract.
*/
export type YamlDocument = {
get(key: string): unknown;
set(key: string, value: unknown): void;
};

type ParseBase = {
source: string;
/**
Expand Down Expand Up @@ -52,14 +61,12 @@ export function parseJson(source: string): { data: any } & ParseBase {
return { data, source, generateCode };
}

export function parseYaml(
source: string
): { data: ReturnType<typeof utils.parseYaml> } & ParseBase {
export function parseYaml(source: string): { data: YamlDocument } & ParseBase {
if (!source) source = '';
const data = utils.parseYaml(source);
const generateCode = () => utils.serializeYaml(data);
const generateCode = () => utils.serializeYaml(data as Parameters<typeof utils.serializeYaml>[0]);

return { data, source, generateCode };
return { data: data as YamlDocument, source, generateCode };
}

export function parseSvelte(source: string): { ast: utils.SvelteAst.Root } & ParseBase {
Expand Down
5 changes: 3 additions & 2 deletions packages/sv-utils/src/tooling/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
parseScript,
parseSvelte,
parseToml,
parseYaml
parseYaml,
type YamlDocument
} from './parsers.ts';
import { type RootWithInstance, ensureScript } from './svelte/index.ts';
import * as svelteNs from './svelte/index.ts';
Expand Down Expand Up @@ -190,7 +191,7 @@ export const transforms = {
* Return `false` from the callback to abort - the original content is returned unchanged.
*/
yaml(
cb: (file: { data: ReturnType<typeof parseYaml>['data']; content: string }) => void | false,
cb: (file: { data: YamlDocument; content: string }) => void | false,
options?: TransformOptions
): TransformFn {
return (content) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/sv/api-surface-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type SetupOptions = {
variants: readonly ProjectVariant[];
clean?: boolean;
};
/** @deprecated Internal helper used by `createSetupTest` - will be removed from public API in a future version. */
declare function setup({ cwd, clean, variants }: SetupOptions): {
templatesDir: string;
};
Expand All @@ -23,11 +24,13 @@ type CreateOptions = {
testName: string;
templatesDir: string;
};
/** @deprecated Internal helper used by `createSetupTest` - will be removed from public API in a future version. */
declare function createProject({ cwd, testName, templatesDir }: CreateOptions): CreateProject;
type PreviewOptions = {
cwd: string;
command?: string;
};
/** @deprecated Internal helper used by `prepareServer` - will be removed from public API in a future version. */
declare function startPreview({ cwd, command }: PreviewOptions): Promise<{
url: string;
close: () => Promise<void>;
Expand Down
Loading
Loading