From 7748eadf710e2c2765e158faf94f374fe252abe9 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Fri, 28 Aug 2026 10:40:59 -0400 Subject: [PATCH 1/8] update xlr compile with package information --- .../commands/xlr-compile-packages.test.ts | 203 ++++++++++++++++++ cli/src/commands/xlr/compile.ts | 95 +++++++- 2 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 cli/src/__tests__/commands/xlr-compile-packages.test.ts diff --git a/cli/src/__tests__/commands/xlr-compile-packages.test.ts b/cli/src/__tests__/commands/xlr-compile-packages.test.ts new file mode 100644 index 0000000..5476e70 --- /dev/null +++ b/cli/src/__tests__/commands/xlr-compile-packages.test.ts @@ -0,0 +1,203 @@ +import fs from "fs"; +import os from "os"; +import path from "path"; +import { test, expect, describe, beforeEach, afterEach } from "vitest"; +import XLRCompile from "../../commands/xlr/compile"; + +/** A plugin package with one asset, laid out the way `xlr compile` expects */ +function writeFixture(dir: string, packageJson?: Record) { + fs.mkdirSync(path.join(dir, "src"), { recursive: true }); + + if (packageJson) { + fs.writeFileSync( + path.join(dir, "package.json"), + JSON.stringify(packageJson), + ); + } + + fs.writeFileSync( + path.join(dir, "src", "index.ts"), + ` +import type { ExtendedPlayerPlugin } from "@player-ui/player"; + +export interface TestAsset { + id: string; + type: "test"; +} + +export class TestPlugin implements ExtendedPlayerPlugin<[TestAsset]> { + name = "test-plugin"; +} +`, + ); +} + +function readManifest(dir: string) { + return JSON.parse( + fs.readFileSync(path.join(dir, "dist", "xlr", "manifest.json"), "utf-8"), + ); +} + +describe("xlr compile package info", () => { + /** An isolated root, so nothing on the ambient filesystem can be picked up */ + let workspace: string; + let cwd: string; + const env = { ...process.env }; + + beforeEach(() => { + workspace = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "xlr-compile-")), + ); + cwd = process.cwd(); + delete process.env.BAZEL_STABLE_STATUS_FILE; + delete process.env.BAZEL_PACKAGE; + }); + + afterEach(() => { + process.chdir(cwd); + fs.rmSync(workspace, { recursive: true, force: true }); + process.env = { ...env }; + }); + + describe("non-bazel", () => { + beforeEach(() => { + process.chdir(workspace); + }); + + test("records the name and version from package.json", async () => { + writeFixture(workspace, { name: "@test/plugin", version: "2.3.4" }); + + await XLRCompile.run(["-i", "src", "-o", "dist"]); + + expect(readManifest(workspace).packages).toStrictEqual({ + react: { name: "@test/plugin", version: "2.3.4" }, + }); + }); + + test("ignores a stamped version when not run under Bazel", async () => { + // package.json is authoritative here, so a stray status file must not override it + writeFixture(workspace, { name: "@test/plugin", version: "2.3.4" }); + const statusFile = path.join(workspace, "stable-status.txt"); + fs.writeFileSync( + statusFile, + "STABLE_GIT_COMMIT abc123\nSTABLE_VERSION 9.9.9\n", + ); + process.env.BAZEL_STABLE_STATUS_FILE = statusFile; + + await XLRCompile.run(["-i", "src", "-o", "dist"]); + + expect(readManifest(workspace).packages).toStrictEqual({ + react: { name: "@test/plugin", version: "2.3.4" }, + }); + }); + + describe("when package.json is missing or incomplete", () => { + test("omits packages when there is no package.json", async () => { + writeFixture(workspace); + + await XLRCompile.run(["-i", "src", "-o", "dist"]); + + expect(readManifest(workspace).packages).toBeUndefined(); + }); + + test("omits packages when package.json has no name", async () => { + writeFixture(workspace, { version: "2.3.4" }); + + await XLRCompile.run(["-i", "src", "-o", "dist"]); + + expect(readManifest(workspace).packages).toBeUndefined(); + }); + }); + }); + + describe("bazel", () => { + // Bazel runs from the workspace root and names the package in BAZEL_PACKAGE, so the + // working directory alone does not identify the package. + const pkgPath = path.join("plugins", "test-plugin"); + + beforeEach(() => { + // a workspace root package.json that must never be picked up + fs.writeFileSync( + path.join(workspace, "package.json"), + JSON.stringify({ name: "workspace-root", version: "0.0.0" }), + ); + process.chdir(workspace); + process.env.BAZEL_PACKAGE = pkgPath; + }); + + test("reads the name from package.json and the version from the stamped status file", async () => { + writeFixture(path.join(workspace, pkgPath), { + name: "@test/plugin", + version: "0.0.0-PLACEHOLDER", + }); + const statusFile = path.join(workspace, "stable-status.txt"); + fs.writeFileSync(statusFile, "STABLE_VERSION 1.1.0\n"); + process.env.BAZEL_STABLE_STATUS_FILE = statusFile; + + await XLRCompile.run([ + "-i", + path.join(pkgPath, "src"), + "-o", + path.join(pkgPath, "dist"), + ]); + + expect( + readManifest(path.join(workspace, pkgPath)).packages, + ).toStrictEqual({ + react: { name: "@test/plugin", version: "1.1.0" }, + }); + }); + + test("omits the version rather than emitting the placeholder, when not stamped", async () => { + writeFixture(path.join(workspace, pkgPath), { + name: "@test/plugin", + version: "0.0.0-PLACEHOLDER", + }); + + await XLRCompile.run([ + "-i", + path.join(pkgPath, "src"), + "-o", + path.join(pkgPath, "dist"), + ]); + + expect( + readManifest(path.join(workspace, pkgPath)).packages, + ).toStrictEqual({ + react: { name: "@test/plugin" }, + }); + }); + + describe("when package.json is missing or incomplete", () => { + test("omits packages when there is no package.json at BAZEL_PACKAGE", async () => { + writeFixture(path.join(workspace, pkgPath)); + + await XLRCompile.run([ + "-i", + path.join(pkgPath, "src"), + "-o", + path.join(pkgPath, "dist"), + ]); + + expect( + readManifest(path.join(workspace, pkgPath)).packages, + ).toBeUndefined(); + }); + + test("omits packages when package.json has no name", async () => { + writeFixture(path.join(workspace, pkgPath), { version: "2.3.4" }); + + await XLRCompile.run([ + "-i", + path.join(pkgPath, "src"), + "-o", + path.join(pkgPath, "dist"), + ]); + + expect( + readManifest(path.join(workspace, pkgPath)).packages, + ).toBeUndefined(); + }); + }); + }); +}); diff --git a/cli/src/commands/xlr/compile.ts b/cli/src/commands/xlr/compile.ts index 17732ab..d365c9d 100644 --- a/cli/src/commands/xlr/compile.ts +++ b/cli/src/commands/xlr/compile.ts @@ -5,7 +5,7 @@ import path from "path"; import globby from "globby"; import logSymbols from "log-symbols"; import { TsConverter } from "@xlr-lib/xlr-converters"; -import type { Manifest } from "@xlr-lib/xlr"; +import type { Manifest, PlatformPackage } from "@xlr-lib/xlr"; import chalk from "chalk"; import { BaseCommand } from "../../utils/base-command"; import { pluginVisitor, fileVisitor } from "../../utils/xlr/visitors"; @@ -52,6 +52,76 @@ export default class XLRCompile extends BaseCommand { }; } + /** + * Get the version Bazel stamped this build with. + */ + private getStampedVersion(): string | undefined { + const statusFile = process.env.BAZEL_STABLE_STATUS_FILE; + + if (!statusFile || !fs.existsSync(statusFile)) { + return undefined; + } + + const line = fs + .readFileSync(statusFile, "utf-8") + .split("\n") + .find((l) => l.startsWith("STABLE_VERSION ")); + + return line?.slice("STABLE_VERSION ".length).trim() || undefined; + } + + /** + * The directory of the package being compiled. + * + * Non-Bazel: the working directory is the package. + * Bazel: runs from the workspace root, so `BAZEL_PACKAGE` is joined onto it. + */ + private getPackageDir(): string { + const bazelPackage = process.env.BAZEL_PACKAGE; + + return bazelPackage + ? path.resolve(process.cwd(), bazelPackage) + : process.cwd(); + } + + /** + * The npm package that provides the capabilities being compiled, read from the + * `package.json` of the package the command was invoked for. + */ + private getReactPackage(): PlatformPackage | undefined { + const packageJsonPath = path.join(this.getPackageDir(), "package.json"); + + let name: unknown; + let version: unknown; + + try { + ({ name, version } = JSON.parse( + fs.readFileSync(packageJsonPath, "utf-8"), + )); + } catch { + this.debug("no readable package.json at %s", packageJsonPath); + return undefined; + } + + if (typeof name !== "string" || !name) { + return undefined; + } + + // Under Bazel the version in package.json is a placeholder that is only substituted at + // publish time, so the stamped value is the only one worth recording. Elsewhere the + // package manager keeps package.json current and it can be read directly. + const resolvedVersion = process.env.BAZEL_PACKAGE + ? this.getStampedVersion() + : version; + + return { + name, + ...(typeof resolvedVersion === "string" && resolvedVersion + ? { version: resolvedVersion } + : {}), + }; + } + async run(): Promise<{ /** the status code */ exitCode: number; @@ -61,8 +131,9 @@ export default class XLRCompile extends BaseCommand { `${inputPath}/**/*.ts`, `${inputPath}/**/*.tsx`, ]); + const reactPackage = this.getReactPackage(); try { - this.processTypes(inputFiles, outputDir, {}, mode); + this.processTypes(inputFiles, outputDir, {}, mode, reactPackage); } catch (e: any) { console.log(""); console.log( @@ -90,6 +161,7 @@ export default class XLRCompile extends BaseCommand { outputDirectory: string, options: ts.CompilerOptions, mode: Mode = Mode.PLUGIN, + reactPackage?: PlatformPackage, ): void { // Build a program using the set of root file names in fileNames const program = ts.createProgram(fileNames, options); @@ -148,11 +220,16 @@ export default class XLRCompile extends BaseCommand { throw new Error("Error: Unable to parse any XLRs in package"); } + const manifest: Manifest = { + ...capabilities, + ...(reactPackage ? { packages: { react: reactPackage } } : {}), + }; + // print out the manifest files - const jsonManifest = JSON.stringify(capabilities, this.replacer, 4); + const jsonManifest = JSON.stringify(manifest, this.replacer, 4); fs.writeFileSync(path.join(outputDirectory, "manifest.json"), jsonManifest); - const tsManifestFile = `${[...(capabilities.capabilities?.values() ?? [])] + const tsManifestFile = `${[...(manifest.capabilities?.values() ?? [])] .flat(2) .map((capability) => { return `const ${capability.replace(".", "_")} = require("./${capability}.json")`; @@ -160,16 +237,20 @@ export default class XLRCompile extends BaseCommand { .join("\n")} module.exports = { - "pluginName": "${capabilities.pluginName}", + "pluginName": "${manifest.pluginName}",${ + manifest.packages + ? `\n "packages": ${JSON.stringify(manifest.packages)},` + : "" + } "capabilities": { - ${[...(capabilities.capabilities?.entries() ?? [])] + ${[...(manifest.capabilities?.entries() ?? [])] .map(([capabilityName, provides]) => { return `"${capabilityName}":[${provides.join(",").replaceAll(".", "_")}],`; }) .join("\n\t\t")} }, "customPrimitives": [ - ${[capabilities.customPrimitives?.map((i) => `"${i}"`).join(",") ?? ""]} + ${[manifest.customPrimitives?.map((i) => `"${i}"`).join(",") ?? ""]} ] } `; From 033720a3192ea5c28a426dce1c4e218c017d17f5 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Fri, 28 Aug 2026 11:48:32 -0400 Subject: [PATCH 2/8] update with XLR_PACKAGE_NAME env --- .../commands/xlr-compile-packages.test.ts | 57 +++++++++--- cli/src/commands/xlr/compile.ts | 86 +++++++++++++------ 2 files changed, 104 insertions(+), 39 deletions(-) diff --git a/cli/src/__tests__/commands/xlr-compile-packages.test.ts b/cli/src/__tests__/commands/xlr-compile-packages.test.ts index 5476e70..a5a4b55 100644 --- a/cli/src/__tests__/commands/xlr-compile-packages.test.ts +++ b/cli/src/__tests__/commands/xlr-compile-packages.test.ts @@ -1,7 +1,7 @@ import fs from "fs"; import os from "os"; import path from "path"; -import { test, expect, describe, beforeEach, afterEach } from "vitest"; +import { test, expect, describe, beforeEach, afterEach, vi } from "vitest"; import XLRCompile from "../../commands/xlr/compile"; /** A plugin package with one asset, laid out the way `xlr compile` expects */ @@ -32,6 +32,13 @@ export class TestPlugin implements ExtendedPlayerPlugin<[TestAsset]> { ); } +/** Silences `this.warn` while capturing what it was called with */ +function spyOnWarn() { + return vi + .spyOn(XLRCompile.prototype, "warn") + .mockImplementation((input) => input); +} + function readManifest(dir: string) { return JSON.parse( fs.readFileSync(path.join(dir, "dist", "xlr", "manifest.json"), "utf-8"), @@ -42,6 +49,7 @@ describe("xlr compile package info", () => { /** An isolated root, so nothing on the ambient filesystem can be picked up */ let workspace: string; let cwd: string; + let warn: ReturnType; const env = { ...process.env }; beforeEach(() => { @@ -49,14 +57,17 @@ describe("xlr compile package info", () => { fs.mkdtempSync(path.join(os.tmpdir(), "xlr-compile-")), ); cwd = process.cwd(); + warn = spyOnWarn(); delete process.env.BAZEL_STABLE_STATUS_FILE; delete process.env.BAZEL_PACKAGE; + delete process.env.XLR_PACKAGE_NAME; }); afterEach(() => { process.chdir(cwd); fs.rmSync(workspace, { recursive: true, force: true }); process.env = { ...env }; + warn.mockRestore(); }); describe("non-bazel", () => { @@ -74,6 +85,16 @@ describe("xlr compile package info", () => { }); }); + test("records the name alone when package.json has no version", async () => { + writeFixture(workspace, { name: "@test/plugin" }); + + await XLRCompile.run(["-i", "src", "-o", "dist"]); + + expect(readManifest(workspace).packages).toStrictEqual({ + react: { name: "@test/plugin" }, + }); + }); + test("ignores a stamped version when not run under Bazel", async () => { // package.json is authoritative here, so a stray status file must not override it writeFixture(workspace, { name: "@test/plugin", version: "2.3.4" }); @@ -91,21 +112,29 @@ describe("xlr compile package info", () => { }); }); + // The omission must be noisy: a manifest silently losing its `packages` key is the + // failure mode this whole path exists to prevent. describe("when package.json is missing or incomplete", () => { - test("omits packages when there is no package.json", async () => { + test("omits packages and warns when there is no package.json", async () => { writeFixture(workspace); await XLRCompile.run(["-i", "src", "-o", "dist"]); expect(readManifest(workspace).packages).toBeUndefined(); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining("No readable package.json"), + ); }); - test("omits packages when package.json has no name", async () => { + test("omits packages and warns when package.json has no name", async () => { writeFixture(workspace, { version: "2.3.4" }); await XLRCompile.run(["-i", "src", "-o", "dist"]); expect(readManifest(workspace).packages).toBeUndefined(); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('No "name" in'), + ); }); }); }); @@ -125,11 +154,10 @@ describe("xlr compile package info", () => { process.env.BAZEL_PACKAGE = pkgPath; }); - test("reads the name from package.json and the version from the stamped status file", async () => { - writeFixture(path.join(workspace, pkgPath), { - name: "@test/plugin", - version: "0.0.0-PLACEHOLDER", - }); + test("takes the name from XLR_PACKAGE_NAME and the version from the stamp", async () => { + // No package.json in the package: Bazel does not stage one, it passes the name instead + writeFixture(path.join(workspace, pkgPath)); + process.env.XLR_PACKAGE_NAME = "@test/plugin"; const statusFile = path.join(workspace, "stable-status.txt"); fs.writeFileSync(statusFile, "STABLE_VERSION 1.1.0\n"); process.env.BAZEL_STABLE_STATUS_FILE = statusFile; @@ -148,11 +176,9 @@ describe("xlr compile package info", () => { }); }); - test("omits the version rather than emitting the placeholder, when not stamped", async () => { - writeFixture(path.join(workspace, pkgPath), { - name: "@test/plugin", - version: "0.0.0-PLACEHOLDER", - }); + test("omits the version when not stamped", async () => { + writeFixture(path.join(workspace, pkgPath)); + process.env.XLR_PACKAGE_NAME = "@test/plugin"; await XLRCompile.run([ "-i", @@ -169,7 +195,7 @@ describe("xlr compile package info", () => { }); describe("when package.json is missing or incomplete", () => { - test("omits packages when there is no package.json at BAZEL_PACKAGE", async () => { + test("omits packages and warns when neither XLR_PACKAGE_NAME nor package.json is available", async () => { writeFixture(path.join(workspace, pkgPath)); await XLRCompile.run([ @@ -182,6 +208,9 @@ describe("xlr compile package info", () => { expect( readManifest(path.join(workspace, pkgPath)).packages, ).toBeUndefined(); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining(path.join(pkgPath, "package.json")), + ); }); test("omits packages when package.json has no name", async () => { diff --git a/cli/src/commands/xlr/compile.ts b/cli/src/commands/xlr/compile.ts index d365c9d..2c4e09d 100644 --- a/cli/src/commands/xlr/compile.ts +++ b/cli/src/commands/xlr/compile.ts @@ -5,7 +5,7 @@ import path from "path"; import globby from "globby"; import logSymbols from "log-symbols"; import { TsConverter } from "@xlr-lib/xlr-converters"; -import type { Manifest, PlatformPackage } from "@xlr-lib/xlr"; +import type { Manifest, PlatformPackages } from "@xlr-lib/xlr"; import chalk from "chalk"; import { BaseCommand } from "../../utils/base-command"; import { pluginVisitor, fileVisitor } from "../../utils/xlr/visitors"; @@ -85,41 +85,77 @@ export default class XLRCompile extends BaseCommand { } /** - * The npm package that provides the capabilities being compiled, read from the - * `package.json` of the package the command was invoked for. + * The npm package that provides the capabilities being compiled. + * + * Bazel supplies the name through `XLR_PACKAGE_NAME`, since it only knows the package path + * and the npm name cannot be derived from it. Otherwise the package's own `package.json` is + * the source. */ - private getReactPackage(): PlatformPackage | undefined { + private getPackages(): PlatformPackages | undefined { + const name = process.env.XLR_PACKAGE_NAME || this.readPackageJsonName(); + + if (!name) { + return undefined; + } + + const version = this.getVersion(); + + // TODO: only `react` is generated, because XLR is compiled from TypeScript and there is no + // equivalent for iOS or Android. Native configurations will be added later. + return { + react: { name, ...(version ? { version } : {}) }, + }; + } + + /** The npm name in the `package.json` of the package being compiled */ + private readPackageJsonName(): string | undefined { const packageJsonPath = path.join(this.getPackageDir(), "package.json"); let name: unknown; - let version: unknown; try { - ({ name, version } = JSON.parse( - fs.readFileSync(packageJsonPath, "utf-8"), - )); + ({ name } = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))); } catch { - this.debug("no readable package.json at %s", packageJsonPath); + this.warn( + `No readable package.json at ${packageJsonPath}. Omitting package information from the manifest.`, + ); return undefined; } if (typeof name !== "string" || !name) { + this.warn( + `No "name" in ${packageJsonPath}. Omitting package information from the manifest.`, + ); return undefined; } - // Under Bazel the version in package.json is a placeholder that is only substituted at - // publish time, so the stamped value is the only one worth recording. Elsewhere the - // package manager keeps package.json current and it can be read directly. - const resolvedVersion = process.env.BAZEL_PACKAGE - ? this.getStampedVersion() - : version; + return name; + } + + /** + * The version to record for the package being compiled. + * + * Under Bazel the version in `package.json` is a placeholder that is only substituted at + * publish time, so the stamped value is the only real source. Elsewhere the package + * manager keeps `package.json` current and it can be read directly. + */ + private getVersion(): string | undefined { + if (process.env.BAZEL_PACKAGE) { + return this.getStampedVersion(); + } + + try { + const { version } = JSON.parse( + fs.readFileSync( + path.join(this.getPackageDir(), "package.json"), + "utf-8", + ), + ); - return { - name, - ...(typeof resolvedVersion === "string" && resolvedVersion - ? { version: resolvedVersion } - : {}), - }; + return typeof version === "string" && version ? version : undefined; + } catch { + return undefined; + } } async run(): Promise<{ @@ -131,9 +167,9 @@ export default class XLRCompile extends BaseCommand { `${inputPath}/**/*.ts`, `${inputPath}/**/*.tsx`, ]); - const reactPackage = this.getReactPackage(); + const packages = this.getPackages(); try { - this.processTypes(inputFiles, outputDir, {}, mode, reactPackage); + this.processTypes(inputFiles, outputDir, {}, mode, packages); } catch (e: any) { console.log(""); console.log( @@ -161,7 +197,7 @@ export default class XLRCompile extends BaseCommand { outputDirectory: string, options: ts.CompilerOptions, mode: Mode = Mode.PLUGIN, - reactPackage?: PlatformPackage, + packages?: PlatformPackages, ): void { // Build a program using the set of root file names in fileNames const program = ts.createProgram(fileNames, options); @@ -222,7 +258,7 @@ export default class XLRCompile extends BaseCommand { const manifest: Manifest = { ...capabilities, - ...(reactPackage ? { packages: { react: reactPackage } } : {}), + ...(packages ? { packages } : {}), }; // print out the manifest files From f45aee6086450fe2fd9fa53e3694cb03a98523e6 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Mon, 31 Aug 2026 09:29:46 -0400 Subject: [PATCH 3/8] bump xlr version --- package.json | 8 +++---- pnpm-lock.yaml | 57 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index e42dc5a..1bfb56d 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ "@oclif/plugin-plugins": "^1.9.0", "@player-lang/react-dsl": "^1.0.1", "@player-lang/json-language-service": "^1.0.1", - "@xlr-lib/xlr": "^1.0.0", - "@xlr-lib/xlr-converters": "^1.0.0", - "@xlr-lib/xlr-sdk": "^1.0.0", - "@xlr-lib/xlr-utils": "^1.0.0", + "@xlr-lib/xlr": "1.1.0-next.0", + "@xlr-lib/xlr-converters": "1.1.0-next.0", + "@xlr-lib/xlr-sdk": "1.1.0-next.0", + "@xlr-lib/xlr-utils": "1.1.0-next.0", "@types/babel__register": "^7.17.0", "@types/fs-extra": "^9.0.13", "@types/mkdirp": "^1.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dfe289..43c17c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,17 +57,17 @@ importers: specifier: ^1.0.1 version: 1.0.4 '@xlr-lib/xlr': - specifier: ^1.0.0 - version: 1.0.0 + specifier: 1.1.0-next.0 + version: 1.1.0-next.0 '@xlr-lib/xlr-converters': - specifier: ^1.0.0 - version: 1.0.0(jsonc-parser@2.3.1) + specifier: 1.1.0-next.0 + version: 1.1.0-next.0(jsonc-parser@2.3.1) '@xlr-lib/xlr-sdk': - specifier: ^1.0.0 - version: 1.0.0 + specifier: 1.1.0-next.0 + version: 1.1.0-next.0 '@xlr-lib/xlr-utils': - specifier: ^1.0.0 - version: 1.0.0(jsonc-parser@2.3.1) + specifier: 1.1.0-next.0 + version: 1.1.0-next.0(jsonc-parser@2.3.1) chalk: specifier: ^4.0.1 version: 4.1.2 @@ -2224,21 +2224,32 @@ packages: '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz} - '@xlr-lib/xlr-converters@1.0.0': - resolution: {integrity: sha512-TovXuu4VMwJBWZnAhhO93jncx5NXfn1khy1mYVIgMXrC7b1c++g6EyxLs4svkUAiQiynEkmSa96QvydSgAeYzw==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-converters/-/xlr-converters-1.0.0.tgz} + '@xlr-lib/xlr-converters@1.1.0-next.0': + resolution: {integrity: sha512-Petyxyt0WmEt8ule4qLh5XgGyW2RSzRTWGjy+xHv1qEBNYX6LdHOe96h9xjIw3fGAe2kpm1KYFpUwn/0ZIjipA==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-converters/-/xlr-converters-1.1.0-next.0.tgz} hasBin: true '@xlr-lib/xlr-sdk@1.0.0': resolution: {integrity: sha512-3ge1NTzCukn9F76HQPZXeNZI/C/HOJF365II6m5GbzhR1Aag9Ghf4/5w5/PIrfb734eZ5kFkMiF6UeMOXUegtQ==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-sdk/-/xlr-sdk-1.0.0.tgz} + '@xlr-lib/xlr-sdk@1.1.0-next.0': + resolution: {integrity: sha512-TG0pHb58/IK/QYh0LNw6OK4/NO4y9hsk6sEw0Z7/PJcIigF6SpevqT2g4vqYC6VnrveGoRcpNFB3fMAw2gJBRA==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-sdk/-/xlr-sdk-1.1.0-next.0.tgz} + '@xlr-lib/xlr-utils@1.0.0': resolution: {integrity: sha512-swoRx3QppioaClwvf+89yJrPJ8oEsIJNWUdney7Vom9XmUKbVY4XgPF0fZSYSC+HWsvB8NbkQ5CNCE8AXW6oxQ==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-utils/-/xlr-utils-1.0.0.tgz} peerDependencies: jsonc-parser: 2.3.1 + '@xlr-lib/xlr-utils@1.1.0-next.0': + resolution: {integrity: sha512-eT9KEwQQLdraJdVDY7lq1YnzQY5kYMJES7EUZtFH2Ge0XkRf9K5kyAGO/BI5x7wGkfPmKtPmaRrXc5IbZA/low==, tarball: https://registry.npmjs.org/@xlr-lib/xlr-utils/-/xlr-utils-1.1.0-next.0.tgz} + peerDependencies: + jsonc-parser: 2.3.1 + '@xlr-lib/xlr@1.0.0': resolution: {integrity: sha512-Zfu6/lLauRQafuUo+JvbKZ2lmQkxtDgJxnGtWieGNct6mSHhWKWRc7siXQlboBxwu84yMcjLSrOxvD9WrBtLWQ==, tarball: https://registry.npmjs.org/@xlr-lib/xlr/-/xlr-1.0.0.tgz} + '@xlr-lib/xlr@1.1.0-next.0': + resolution: {integrity: sha512-ZfiMna7EekGVMriUUIUsMPLmpwlHC4GFpbW4pZ4XHm2lAOwC/obA6/29Bt7InlBPrrImqLF50wfmXE1jz5TWZA==, tarball: https://registry.npmjs.org/@xlr-lib/xlr/-/xlr-1.1.0-next.0.tgz} + '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==, tarball: https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz} @@ -7891,10 +7902,10 @@ snapshots: loupe: 3.2.1 tinyrainbow: 1.2.0 - '@xlr-lib/xlr-converters@1.0.0(jsonc-parser@2.3.1)': + '@xlr-lib/xlr-converters@1.1.0-next.0(jsonc-parser@2.3.1)': dependencies: - '@xlr-lib/xlr': 1.0.0 - '@xlr-lib/xlr-utils': 1.0.0(jsonc-parser@2.3.1) + '@xlr-lib/xlr': 1.1.0-next.0 + '@xlr-lib/xlr-utils': 1.1.0-next.0(jsonc-parser@2.3.1) tslib: 2.8.1 typescript: 5.5.4 transitivePeerDependencies: @@ -7910,16 +7921,36 @@ snapshots: jsonc-parser: 2.3.1 tslib: 2.8.1 + '@xlr-lib/xlr-sdk@1.1.0-next.0': + dependencies: + '@types/fs-extra': 9.0.13 + '@types/node': 18.19.130 + '@xlr-lib/xlr': 1.1.0-next.0 + '@xlr-lib/xlr-utils': 1.1.0-next.0(jsonc-parser@2.3.1) + fs-extra: 10.1.0 + jsonc-parser: 2.3.1 + tslib: 2.8.1 + '@xlr-lib/xlr-utils@1.0.0(jsonc-parser@2.3.1)': dependencies: '@xlr-lib/xlr': 1.0.0 jsonc-parser: 2.3.1 tslib: 2.8.1 + '@xlr-lib/xlr-utils@1.1.0-next.0(jsonc-parser@2.3.1)': + dependencies: + '@xlr-lib/xlr': 1.1.0-next.0 + jsonc-parser: 2.3.1 + tslib: 2.8.1 + '@xlr-lib/xlr@1.0.0': dependencies: tslib: 2.8.1 + '@xlr-lib/xlr@1.1.0-next.0': + dependencies: + tslib: 2.8.1 + '@yarnpkg/lockfile@1.1.0': {} acorn-jsx@5.3.2(acorn@8.15.0): From f40a1c441dbfc7adf1e324141eb278f0c78015bf Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Mon, 31 Aug 2026 09:40:34 -0400 Subject: [PATCH 4/8] bump codecov version --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc90bac..69b3de3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ parameters: default: false orbs: - codecov: codecov/codecov@5.4.3 + codecov: codecov/codecov@6.0.0 executors: base: From 15271b7a5e130ecc3e6009ed9595b54e8cb481ea Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Tue, 1 Sep 2026 15:53:34 -0400 Subject: [PATCH 5/8] extract util func --- cli/src/commands/xlr/compile.ts | 109 +------------------------- cli/src/utils/xlr/packages.ts | 130 ++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 107 deletions(-) create mode 100644 cli/src/utils/xlr/packages.ts diff --git a/cli/src/commands/xlr/compile.ts b/cli/src/commands/xlr/compile.ts index 2c4e09d..da1f063 100644 --- a/cli/src/commands/xlr/compile.ts +++ b/cli/src/commands/xlr/compile.ts @@ -10,6 +10,7 @@ import chalk from "chalk"; import { BaseCommand } from "../../utils/base-command"; import { pluginVisitor, fileVisitor } from "../../utils/xlr/visitors"; import { Mode, customPrimitives } from "../../utils/xlr/consts"; +import { getPackages } from "../../utils/xlr/packages"; /** * Exports TS Interfaces/Types to XLR format @@ -52,112 +53,6 @@ export default class XLRCompile extends BaseCommand { }; } - /** - * Get the version Bazel stamped this build with. - */ - private getStampedVersion(): string | undefined { - const statusFile = process.env.BAZEL_STABLE_STATUS_FILE; - - if (!statusFile || !fs.existsSync(statusFile)) { - return undefined; - } - - const line = fs - .readFileSync(statusFile, "utf-8") - .split("\n") - .find((l) => l.startsWith("STABLE_VERSION ")); - - return line?.slice("STABLE_VERSION ".length).trim() || undefined; - } - - /** - * The directory of the package being compiled. - * - * Non-Bazel: the working directory is the package. - * Bazel: runs from the workspace root, so `BAZEL_PACKAGE` is joined onto it. - */ - private getPackageDir(): string { - const bazelPackage = process.env.BAZEL_PACKAGE; - - return bazelPackage - ? path.resolve(process.cwd(), bazelPackage) - : process.cwd(); - } - - /** - * The npm package that provides the capabilities being compiled. - * - * Bazel supplies the name through `XLR_PACKAGE_NAME`, since it only knows the package path - * and the npm name cannot be derived from it. Otherwise the package's own `package.json` is - * the source. - */ - private getPackages(): PlatformPackages | undefined { - const name = process.env.XLR_PACKAGE_NAME || this.readPackageJsonName(); - - if (!name) { - return undefined; - } - - const version = this.getVersion(); - - // TODO: only `react` is generated, because XLR is compiled from TypeScript and there is no - // equivalent for iOS or Android. Native configurations will be added later. - return { - react: { name, ...(version ? { version } : {}) }, - }; - } - - /** The npm name in the `package.json` of the package being compiled */ - private readPackageJsonName(): string | undefined { - const packageJsonPath = path.join(this.getPackageDir(), "package.json"); - - let name: unknown; - - try { - ({ name } = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))); - } catch { - this.warn( - `No readable package.json at ${packageJsonPath}. Omitting package information from the manifest.`, - ); - return undefined; - } - - if (typeof name !== "string" || !name) { - this.warn( - `No "name" in ${packageJsonPath}. Omitting package information from the manifest.`, - ); - return undefined; - } - - return name; - } - - /** - * The version to record for the package being compiled. - * - * Under Bazel the version in `package.json` is a placeholder that is only substituted at - * publish time, so the stamped value is the only real source. Elsewhere the package - * manager keeps `package.json` current and it can be read directly. - */ - private getVersion(): string | undefined { - if (process.env.BAZEL_PACKAGE) { - return this.getStampedVersion(); - } - - try { - const { version } = JSON.parse( - fs.readFileSync( - path.join(this.getPackageDir(), "package.json"), - "utf-8", - ), - ); - - return typeof version === "string" && version ? version : undefined; - } catch { - return undefined; - } - } - async run(): Promise<{ /** the status code */ exitCode: number; @@ -167,7 +62,7 @@ export default class XLRCompile extends BaseCommand { `${inputPath}/**/*.ts`, `${inputPath}/**/*.tsx`, ]); - const packages = this.getPackages(); + const packages = getPackages((message) => this.warn(message)); try { this.processTypes(inputFiles, outputDir, {}, mode, packages); } catch (e: any) { diff --git a/cli/src/utils/xlr/packages.ts b/cli/src/utils/xlr/packages.ts new file mode 100644 index 0000000..17f7191 --- /dev/null +++ b/cli/src/utils/xlr/packages.ts @@ -0,0 +1,130 @@ +import fs from "fs"; +import path from "path"; +import type { PlatformPackages } from "@xlr-lib/xlr"; + +/** + * Where the npm name and version of the package being compiled come from. + * + * | | name | version | + * | --- | --- | --- | + * | Bazel (`BAZEL_PACKAGE` is set) | `XLR_PACKAGE_NAME`, else the package's `package.json` | the `STABLE_VERSION` stamp | + * | anywhere else | `XLR_PACKAGE_NAME`, else the package's `package.json` | the package's `package.json` | + * + * Only the version differs between the two: under Bazel the version in `package.json` is a + * placeholder that is substituted at publish time, so the stamp is the only real source. + * Elsewhere the package manager keeps `package.json` current and it is read directly. + */ + +export type WarnFn = (message: string) => void; + +/** Whether this compile is running as a Bazel action */ +function isBazel(): boolean { + return Boolean(process.env.BAZEL_PACKAGE); +} + +/** + * The directory of the package being compiled. + * + * Bazel runs from the workspace root and names the package in `BAZEL_PACKAGE`; everywhere + * else the working directory is already the package. + */ +function getPackageDir(): string { + const bazelPackage = process.env.BAZEL_PACKAGE; + + return bazelPackage + ? path.resolve(process.cwd(), bazelPackage) + : process.cwd(); +} + +/** The parsed `package.json` of the package being compiled, or undefined if there isn't a readable one */ +function getPackageJson( + packageDir: string, +): Record | undefined { + try { + return JSON.parse( + fs.readFileSync(path.join(packageDir, "package.json"), "utf-8"), + ); + } catch { + return undefined; + } +} + +/** The `name` of a `package.json`, warning about whatever made it unavailable */ +function getPackageJsonName( + packageDir: string, + packageJson: Record | undefined, + warn: WarnFn, +): string | undefined { + const packageJsonPath = path.join(packageDir, "package.json"); + + if (!packageJson) { + warn( + `No readable package.json at ${packageJsonPath}. Omitting package information from the manifest.`, + ); + return undefined; + } + + const { name } = packageJson; + + if (typeof name !== "string" || !name) { + warn( + `No "name" in ${packageJsonPath}. Omitting package information from the manifest.`, + ); + return undefined; + } + + return name; +} + +/** The `version` of a `package.json` */ +function getPackageJsonVersion( + packageJson: Record | undefined, +): string | undefined { + const version = packageJson?.version; + + return typeof version === "string" && version ? version : undefined; +} + +/** The version Bazel stamped this build with, read from the stable status file */ +function getStampedVersion(): string | undefined { + const statusFile = process.env.BAZEL_STABLE_STATUS_FILE; + + if (!statusFile || !fs.existsSync(statusFile)) { + return undefined; + } + + const line = fs + .readFileSync(statusFile, "utf-8") + .split("\n") + .find((l) => l.startsWith("STABLE_VERSION ")); + + return line?.slice("STABLE_VERSION ".length).trim() || undefined; +} + +/** + * The npm package that provides the capabilities being compiled, or undefined if its name + * cannot be determined. + */ +export function getPackages(warn: WarnFn): PlatformPackages | undefined { + const packageDir = getPackageDir(); + const packageJson = getPackageJson(packageDir); + + // Bazel only knows the package path, so it passes the npm name through the environment. + const name = + process.env.XLR_PACKAGE_NAME || + getPackageJsonName(packageDir, packageJson, warn); + + if (!name) { + return undefined; + } + + const version = isBazel() + ? getStampedVersion() + : getPackageJsonVersion(packageJson); + + // TODO: only `react` is generated, because XLR is compiled from TypeScript and there is no + // equivalent for iOS or Android. Native configurations will be added later. + return { + react: { name, ...(version ? { version } : {}) }, + }; +} From 8be7074aebf1a89f37088af775feb2347c6f7760 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Thu, 3 Sep 2026 14:17:13 -0400 Subject: [PATCH 6/8] fix version missing due to status file due to execroot change in js_binary --- .../commands/xlr-compile-packages.test.ts | 49 ++++++++++++------- cli/src/utils/xlr/packages.ts | 26 +++++----- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/cli/src/__tests__/commands/xlr-compile-packages.test.ts b/cli/src/__tests__/commands/xlr-compile-packages.test.ts index a5a4b55..d02b95d 100644 --- a/cli/src/__tests__/commands/xlr-compile-packages.test.ts +++ b/cli/src/__tests__/commands/xlr-compile-packages.test.ts @@ -61,6 +61,7 @@ describe("xlr compile package info", () => { delete process.env.BAZEL_STABLE_STATUS_FILE; delete process.env.BAZEL_PACKAGE; delete process.env.XLR_PACKAGE_NAME; + delete process.env.JS_BINARY__EXECROOT; }); afterEach(() => { @@ -95,23 +96,6 @@ describe("xlr compile package info", () => { }); }); - test("ignores a stamped version when not run under Bazel", async () => { - // package.json is authoritative here, so a stray status file must not override it - writeFixture(workspace, { name: "@test/plugin", version: "2.3.4" }); - const statusFile = path.join(workspace, "stable-status.txt"); - fs.writeFileSync( - statusFile, - "STABLE_GIT_COMMIT abc123\nSTABLE_VERSION 9.9.9\n", - ); - process.env.BAZEL_STABLE_STATUS_FILE = statusFile; - - await XLRCompile.run(["-i", "src", "-o", "dist"]); - - expect(readManifest(workspace).packages).toStrictEqual({ - react: { name: "@test/plugin", version: "2.3.4" }, - }); - }); - // The omission must be noisy: a manifest silently losing its `packages` key is the // failure mode this whole path exists to prevent. describe("when package.json is missing or incomplete", () => { @@ -176,6 +160,37 @@ describe("xlr compile package info", () => { }); }); + test("resolves an execroot-relative stamp path against the execroot", async () => { + // Bazel names the status file relative to the execroot, but the js_binary launcher + // runs the tool from BAZEL_BINDIR, so a relative path does not resolve against cwd. + writeFixture(path.join(workspace, pkgPath)); + process.env.XLR_PACKAGE_NAME = "@test/plugin"; + fs.mkdirSync(path.join(workspace, "bazel-out"), { recursive: true }); + fs.writeFileSync( + path.join(workspace, "bazel-out", "stable-status.txt"), + "STABLE_VERSION 1.2.0-next.7\n", + ); + process.env.BAZEL_STABLE_STATUS_FILE = "bazel-out/stable-status.txt"; + process.env.JS_BINARY__EXECROOT = workspace; + + const bindir = path.join(workspace, "bazel-out", "bin"); + fs.mkdirSync(bindir, { recursive: true }); + process.chdir(bindir); + + await XLRCompile.run([ + "-i", + path.join(workspace, pkgPath, "src"), + "-o", + path.join(workspace, pkgPath, "dist"), + ]); + + expect( + readManifest(path.join(workspace, pkgPath)).packages, + ).toStrictEqual({ + react: { name: "@test/plugin", version: "1.2.0-next.7" }, + }); + }); + test("omits the version when not stamped", async () => { writeFixture(path.join(workspace, pkgPath)); process.env.XLR_PACKAGE_NAME = "@test/plugin"; diff --git a/cli/src/utils/xlr/packages.ts b/cli/src/utils/xlr/packages.ts index 17f7191..5d48fb5 100644 --- a/cli/src/utils/xlr/packages.ts +++ b/cli/src/utils/xlr/packages.ts @@ -7,7 +7,7 @@ import type { PlatformPackages } from "@xlr-lib/xlr"; * * | | name | version | * | --- | --- | --- | - * | Bazel (`BAZEL_PACKAGE` is set) | `XLR_PACKAGE_NAME`, else the package's `package.json` | the `STABLE_VERSION` stamp | + * | Bazel (stamped) | `XLR_PACKAGE_NAME`, else the package's `package.json` | the `STABLE_VERSION` stamp | * | anywhere else | `XLR_PACKAGE_NAME`, else the package's `package.json` | the package's `package.json` | * * Only the version differs between the two: under Bazel the version in `package.json` is a @@ -17,11 +17,6 @@ import type { PlatformPackages } from "@xlr-lib/xlr"; export type WarnFn = (message: string) => void; -/** Whether this compile is running as a Bazel action */ -function isBazel(): boolean { - return Boolean(process.env.BAZEL_PACKAGE); -} - /** * The directory of the package being compiled. * @@ -89,12 +84,22 @@ function getPackageJsonVersion( function getStampedVersion(): string | undefined { const statusFile = process.env.BAZEL_STABLE_STATUS_FILE; - if (!statusFile || !fs.existsSync(statusFile)) { + if (!statusFile) { + return undefined; + } + + // Bazel names the status file relative to the execroot (`File.path`), but the js_binary + // launcher changes directory out of the execroot into BAZEL_BINDIR before running the + // tool, so re-anchor the path before reading it. + const execroot = process.env.JS_BINARY__EXECROOT; + const resolved = execroot ? path.join(execroot, statusFile) : statusFile; + + if (!fs.existsSync(resolved)) { return undefined; } const line = fs - .readFileSync(statusFile, "utf-8") + .readFileSync(resolved, "utf-8") .split("\n") .find((l) => l.startsWith("STABLE_VERSION ")); @@ -118,9 +123,8 @@ export function getPackages(warn: WarnFn): PlatformPackages | undefined { return undefined; } - const version = isBazel() - ? getStampedVersion() - : getPackageJsonVersion(packageJson); + // Only a stamped Bazel build produces a status file; otherwise `package.json` is the source. + const version = getStampedVersion() ?? getPackageJsonVersion(packageJson); // TODO: only `react` is generated, because XLR is compiled from TypeScript and there is no // equivalent for iOS or Android. Native configurations will be added later. From 055f239dec183533c40131f568cd0b9f4fc1a02c Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Thu, 3 Sep 2026 16:37:39 -0400 Subject: [PATCH 7/8] update with oclif Error.warn --- cli/src/commands/xlr/compile.ts | 2 +- cli/src/utils/xlr/packages.ts | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/cli/src/commands/xlr/compile.ts b/cli/src/commands/xlr/compile.ts index da1f063..c0f9411 100644 --- a/cli/src/commands/xlr/compile.ts +++ b/cli/src/commands/xlr/compile.ts @@ -62,7 +62,7 @@ export default class XLRCompile extends BaseCommand { `${inputPath}/**/*.ts`, `${inputPath}/**/*.tsx`, ]); - const packages = getPackages((message) => this.warn(message)); + const packages = getPackages(); try { this.processTypes(inputFiles, outputDir, {}, mode, packages); } catch (e: any) { diff --git a/cli/src/utils/xlr/packages.ts b/cli/src/utils/xlr/packages.ts index 5d48fb5..f1039f1 100644 --- a/cli/src/utils/xlr/packages.ts +++ b/cli/src/utils/xlr/packages.ts @@ -1,5 +1,6 @@ import fs from "fs"; import path from "path"; +import { Errors } from "@oclif/core"; import type { PlatformPackages } from "@xlr-lib/xlr"; /** @@ -15,8 +16,6 @@ import type { PlatformPackages } from "@xlr-lib/xlr"; * Elsewhere the package manager keeps `package.json` current and it is read directly. */ -export type WarnFn = (message: string) => void; - /** * The directory of the package being compiled. * @@ -35,36 +34,34 @@ function getPackageDir(): string { function getPackageJson( packageDir: string, ): Record | undefined { + const packageJsonPath = path.join(packageDir, "package.json"); + try { - return JSON.parse( - fs.readFileSync(path.join(packageDir, "package.json"), "utf-8"), + return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + } catch (error) { + Errors.warn( + `Could not read ${packageJsonPath}: ${ + error instanceof Error ? error.message : String(error) + }`, ); - } catch { return undefined; } } -/** The `name` of a `package.json`, warning about whatever made it unavailable */ +/** The `name` of a `package.json`, warning if there isn't one */ function getPackageJsonName( packageDir: string, packageJson: Record | undefined, - warn: WarnFn, ): string | undefined { - const packageJsonPath = path.join(packageDir, "package.json"); - if (!packageJson) { - warn( - `No readable package.json at ${packageJsonPath}. Omitting package information from the manifest.`, - ); + // getPackageJson already warned about why it is unavailable return undefined; } const { name } = packageJson; if (typeof name !== "string" || !name) { - warn( - `No "name" in ${packageJsonPath}. Omitting package information from the manifest.`, - ); + Errors.warn(`No "name" in ${path.join(packageDir, "package.json")}.`); return undefined; } @@ -110,16 +107,16 @@ function getStampedVersion(): string | undefined { * The npm package that provides the capabilities being compiled, or undefined if its name * cannot be determined. */ -export function getPackages(warn: WarnFn): PlatformPackages | undefined { +export function getPackages(): PlatformPackages | undefined { const packageDir = getPackageDir(); const packageJson = getPackageJson(packageDir); // Bazel only knows the package path, so it passes the npm name through the environment. const name = - process.env.XLR_PACKAGE_NAME || - getPackageJsonName(packageDir, packageJson, warn); + process.env.XLR_PACKAGE_NAME || getPackageJsonName(packageDir, packageJson); if (!name) { + Errors.warn("Omitting package information from the manifest."); return undefined; } From 8f6ec4336144af9c5935abc5fea9924016aeac37 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Thu, 3 Sep 2026 16:44:16 -0400 Subject: [PATCH 8/8] fix spyon --- .../__tests__/commands/xlr-compile-packages.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cli/src/__tests__/commands/xlr-compile-packages.test.ts b/cli/src/__tests__/commands/xlr-compile-packages.test.ts index d02b95d..7922bbb 100644 --- a/cli/src/__tests__/commands/xlr-compile-packages.test.ts +++ b/cli/src/__tests__/commands/xlr-compile-packages.test.ts @@ -2,6 +2,7 @@ import fs from "fs"; import os from "os"; import path from "path"; import { test, expect, describe, beforeEach, afterEach, vi } from "vitest"; +import { Errors } from "@oclif/core"; import XLRCompile from "../../commands/xlr/compile"; /** A plugin package with one asset, laid out the way `xlr compile` expects */ @@ -32,11 +33,9 @@ export class TestPlugin implements ExtendedPlayerPlugin<[TestAsset]> { ); } -/** Silences `this.warn` while capturing what it was called with */ +/** Silences `Errors.warn` while capturing what it was called with */ function spyOnWarn() { - return vi - .spyOn(XLRCompile.prototype, "warn") - .mockImplementation((input) => input); + return vi.spyOn(Errors, "warn").mockImplementation(() => undefined); } function readManifest(dir: string) { @@ -106,7 +105,10 @@ describe("xlr compile package info", () => { expect(readManifest(workspace).packages).toBeUndefined(); expect(warn).toHaveBeenCalledWith( - expect.stringContaining("No readable package.json"), + expect.stringContaining("Could not read"), + ); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining("Omitting package information"), ); });