From 68a2ae6d2eb4061a79f6c04ea2adb006ad3c6539 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Wed, 10 Dec 2025 17:00:27 -0800 Subject: [PATCH 1/2] Add ability to control peer/dev deps. --- .../src/components/PackageDirectory.tsx | 52 +++++++++++- .../typescript/src/components/PackageJson.tsx | 44 ++++++---- .../src/components/TypeDeclaration.tsx | 2 +- packages/typescript/src/context/index.ts | 1 + .../src/context/package-metadata.ts | 13 +++ packages/typescript/src/create-package.ts | 22 ++++- packages/typescript/test/externals.test.tsx | 82 +++++++++++++++++++ 7 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 packages/typescript/src/context/package-metadata.ts diff --git a/packages/typescript/src/components/PackageDirectory.tsx b/packages/typescript/src/components/PackageDirectory.tsx index 0a337cb99..04d88a7e3 100644 --- a/packages/typescript/src/components/PackageDirectory.tsx +++ b/packages/typescript/src/components/PackageDirectory.tsx @@ -9,8 +9,11 @@ import { SourceDirectoryContext, splitProps, useContext, + Wrap, } from "@alloy-js/core"; import { join } from "pathe"; +import { PackageMetadataContext } from "../context/package-metadata.js"; +import { ExternalPackage, getPackageScope } from "../create-package.js"; import { TSPackageScope } from "../symbols/index.js"; import { modulePath } from "../utils.js"; import { PackageJsonFile, PackageJsonFileProps } from "./PackageJson.js"; @@ -20,6 +23,23 @@ export interface PackageDirectoryProps extends PackageJsonFileProps { tsConfig?: { outDir?: string }; children?: Children; path?: string; + + /** + * The versions to use for external packages referenced within this package. + * By default, the version specified when the external package was created is + * used. + */ + packageVersions?: [ExternalPackage, string][]; + + /** + * The package dependency kinds to use for external packages referenced within + * this package. By default, all external packages are added as + * "dependencies". + */ + packageDependencyKinds?: [ + ExternalPackage, + "dependencies" | "peerDependencies" | "devDependencies", + ][]; } export const PackageContext: ComponentContext = @@ -58,13 +78,39 @@ export function PackageDirectory(props: PackageDirectoryProps) { "devDependencies", ]); + let pkgMeta: PackageMetadataContext | undefined = undefined; + if (props.packageVersions || props.packageDependencyKinds) { + pkgMeta = { + versionSpecifiers: new Map(), + dependencyType: new Map(), + }; + + if (props.packageVersions) { + for (const [pkg, version] of props.packageVersions) { + pkgMeta.versionSpecifiers.set(getPackageScope(pkg), version); + } + } + + if (props.packageDependencyKinds) { + for (const [pkg, kind] of props.packageDependencyKinds) { + pkgMeta.dependencyType.set(getPackageScope(pkg), kind); + } + } + } + return ( - - - {props.children} + + + + {props.children} + diff --git a/packages/typescript/src/components/PackageJson.tsx b/packages/typescript/src/components/PackageJson.tsx index 50fae9b41..63aedf70f 100644 --- a/packages/typescript/src/components/PackageJson.tsx +++ b/packages/typescript/src/components/PackageJson.tsx @@ -1,4 +1,5 @@ -import { memo, SourceFile } from "@alloy-js/core"; +import { memo, SourceFile, useContext } from "@alloy-js/core"; +import { PackageMetadataContext } from "../context/package-metadata.js"; import { modulePath } from "../utils.js"; import { usePackage } from "./PackageDirectory.js"; @@ -73,8 +74,32 @@ export interface PackageExports { */ export function PackageJsonFile(props: PackageJsonFileProps) { const pkg = usePackage(); + const pkgMeta = useContext(PackageMetadataContext); + + const dependencies = memo(() => { + const kinds = { + dependencies: props.dependencies, + devDependencies: props.devDependencies, + peerDependencies: props.peerDependencies, + }; + + if (pkg) { + for (const dependency of pkg.scope.dependencies) { + const kind = pkgMeta?.dependencyType.get(dependency) ?? "dependencies"; + const versionSpecifier = + pkgMeta?.versionSpecifiers.get(dependency) ?? dependency.version; + + kinds[kind] ??= {}; + kinds[kind][dependency.name] = versionSpecifier; + } + } + + return kinds; + }); const jsonContent = memo(() => { + const deps = dependencies(); + const pkgJson = { name: props.name, version: props.version, @@ -83,20 +108,9 @@ export function PackageJsonFile(props: PackageJsonFileProps) { license: props.license, homepage: props.homepage, type: props.type ?? "module", - dependencies: - props.dependencies || (pkg && pkg.scope.dependencies.size > 0) ? - Object.fromEntries([ - ...Object.entries(props.dependencies ?? {}), - ...(pkg ? - Array.from(pkg.scope.dependencies).map((dependency) => [ - dependency.name, - dependency.version, - ]) - : []), - ]) - : undefined, - devDependencies: props.devDependencies, - peerDependencies: props.peerDependencies, + dependencies: deps.dependencies, + devDependencies: deps.devDependencies, + peerDependencies: deps.peerDependencies, scripts: props.scripts, exports: undefined as any, }; diff --git a/packages/typescript/src/components/TypeDeclaration.tsx b/packages/typescript/src/components/TypeDeclaration.tsx index c2018ca18..10eaac332 100644 --- a/packages/typescript/src/components/TypeDeclaration.tsx +++ b/packages/typescript/src/components/TypeDeclaration.tsx @@ -13,7 +13,7 @@ export const TypeDeclaration = ensureTypeRefContext( - + type = {props.children}; diff --git a/packages/typescript/src/context/index.ts b/packages/typescript/src/context/index.ts index 4489afb1f..a9f0b2b64 100644 --- a/packages/typescript/src/context/index.ts +++ b/packages/typescript/src/context/index.ts @@ -1 +1,2 @@ // type ref context isn't exported because it conflicts with the component of the same name. +export * from "./package-metadata.js"; diff --git a/packages/typescript/src/context/package-metadata.ts b/packages/typescript/src/context/package-metadata.ts new file mode 100644 index 000000000..8c7a8a188 --- /dev/null +++ b/packages/typescript/src/context/package-metadata.ts @@ -0,0 +1,13 @@ +import { createNamedContext } from "@alloy-js/core"; +import { TSPackageScope } from "../symbols/ts-package-scope.js"; + +export interface PackageMetadataContext { + versionSpecifiers: Map; + dependencyType: Map< + TSPackageScope, + "dependencies" | "peerDependencies" | "devDependencies" + >; +} + +export const PackageMetadataContext = + createNamedContext("PackageMetadataContext"); diff --git a/packages/typescript/src/create-package.ts b/packages/typescript/src/create-package.ts index 1030e8a6b..f5a8ac3d1 100644 --- a/packages/typescript/src/create-package.ts +++ b/packages/typescript/src/create-package.ts @@ -165,10 +165,19 @@ function assignMembers( } } +const packageScopeSymbol: unique symbol = Symbol(); +/** + * Retrieve the package scope associated with an external package created via + * createPackage. + */ +export function getPackageScope(pgk: ExternalPackage) { + return (pgk as any)[packageScopeSymbol]; +} + function createSymbols( binder: Binder, props: CreatePackageProps, - refkeys: Record, + refkeys: Record, ) { const pkgScope = new TSPackageScope( props.name, @@ -180,6 +189,8 @@ function createSymbols( }, ); + refkeys[packageScopeSymbol] = pkgScope; + for (const [path, symbols] of Object.entries(props.descriptor)) { const keys = path === "." ? refkeys : refkeys[path]; const moduleScope = new TSModuleScope(path, pkgScope, { @@ -252,13 +263,20 @@ function createRefkeysForMembers( } } +export interface ExternalPackage { + [externalPackageSymbol]: true; +} + +const externalPackageSymbol: unique symbol = Symbol("ExternalPackageSymbol"); + export function createPackage( props: CreatePackageProps, -): PackageRefkeys & SymbolCreator { +): PackageRefkeys & SymbolCreator & ExternalPackage { const refkeys: any = { [getSymbolCreatorSymbol()](binder: Binder) { createSymbols(binder, props, refkeys); }, + [externalPackageSymbol]: true, }; for (const [path, symbols] of Object.entries(props.descriptor)) { diff --git a/packages/typescript/test/externals.test.tsx b/packages/typescript/test/externals.test.tsx index 8842e039f..ced6e3948 100644 --- a/packages/typescript/test/externals.test.tsx +++ b/packages/typescript/test/externals.test.tsx @@ -187,3 +187,85 @@ it("can import static members", () => { `, }); }); + +it("can specify packages as dev dependencies", () => { + const testLib = createPackage({ + name: "testLib", + version: "1.0.0", + descriptor: { + ".": { + named: ["foo"], + }, + }, + }); + + expect( + + + {testLib.foo}; + + , + ).toRenderTo({ + "package.json": ` + { + "name": "test", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "typescript": "^5.5.2", + "testLib": "2.0.0" + } + } + `, + "tsconfig.json": expect.anything(), + "index.ts": expect.anything(), + }); +}); + +it("can specify packages as peer dependencies", () => { + const testLib = createPackage({ + name: "testLib", + version: "1.0.0", + descriptor: { + ".": { + named: ["foo"], + }, + }, + }); + + expect( + + + {testLib.foo}; + + , + ).toRenderTo({ + "package.json": ` + { + "name": "test", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "typescript": "^5.5.2" + }, + "peerDependencies": { + "testLib": "2.0.0" + } + } + `, + "tsconfig.json": expect.anything(), + "index.ts": expect.anything(), + }); +}); From a335a493a519b99e3f7872f017782755a5a63f14 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Wed, 10 Dec 2025 17:02:22 -0800 Subject: [PATCH 2/2] changes --- .chronus/changes/peer-deps-2025-11-10-17-1-32.md | 7 +++++++ .chronus/changes/peer-deps-2025-11-10-17-2-4.md | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 .chronus/changes/peer-deps-2025-11-10-17-1-32.md create mode 100644 .chronus/changes/peer-deps-2025-11-10-17-2-4.md diff --git a/.chronus/changes/peer-deps-2025-11-10-17-1-32.md b/.chronus/changes/peer-deps-2025-11-10-17-1-32.md new file mode 100644 index 000000000..d1807499c --- /dev/null +++ b/.chronus/changes/peer-deps-2025-11-10-17-1-32.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@alloy-js/typescript" +--- + +The PackageDirectory component can now specify what kind of dependency to create for package dependencies added by reference using the `packageDependencyKinds` prop. \ No newline at end of file diff --git a/.chronus/changes/peer-deps-2025-11-10-17-2-4.md b/.chronus/changes/peer-deps-2025-11-10-17-2-4.md new file mode 100644 index 000000000..e7a2eb8df --- /dev/null +++ b/.chronus/changes/peer-deps-2025-11-10-17-2-4.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@alloy-js/typescript" +--- + +The PackageDirectory component can now specify what version of a dependency to create for package dependencies added by reference using the `packageVersions` prop. \ No newline at end of file