FN-5916: fix CLI bin targets for pnpm install
Use a committed launcher for published CLI bin links to avoid fresh-install warnings. - point the published fn and fusion bin entries at a committed bin.mjs launcher - add a launcher that checks for dist/bin.js and forwards execution to the built CLI - cover bin target invariants across workspace packages and update CLI package config tests - add a patch changeset for the published @runfusion/fusion package Files changed: .changeset/fn-5916-cli-bin-launcher.md | 5 ++ packages/cli/bin.mjs | 20 ++++++ packages/cli/package.json | 5 +- packages/cli/src/__tests__/bin-targets.test.ts | 85 +++++++++++++++++++++++ packages/cli/src/__tests__/package-config.test.ts | 8 ++- 5 files changed, 118 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-5916 Fusion-Task-Lineage: 59d40654-ff77-4655-b9ce-3f3421521b2c
This commit is contained in:
5
.changeset/fn-5916-cli-bin-launcher.md
Normal file
5
.changeset/fn-5916-cli-bin-launcher.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix fresh-install `pnpm install` bin-link warnings by pointing the published `fn`/`fusion` bins at a committed `bin.mjs` launcher that forwards to the built CLI output.
|
||||||
20
packages/cli/bin.mjs
Executable file
20
packages/cli/bin.mjs
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { constants } from "node:fs";
|
||||||
|
import { access } from "node:fs/promises";
|
||||||
|
import { dirname, resolve } from "node:path";
|
||||||
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
|
|
||||||
|
const packageDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const distEntry = resolve(packageDir, "dist", "bin.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await access(distEntry, constants.F_OK);
|
||||||
|
} catch {
|
||||||
|
globalThis.console.error(
|
||||||
|
`Fusion CLI build output is missing at ${distEntry}. Run \`pnpm build\` before invoking this source checkout.`,
|
||||||
|
);
|
||||||
|
globalThis.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await import(pathToFileURL(distEntry).href);
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
"pi-package"
|
"pi-package"
|
||||||
],
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"fn": "./dist/bin.js",
|
"fn": "./bin.mjs",
|
||||||
"fusion": "./dist/bin.js"
|
"fusion": "./bin.mjs"
|
||||||
},
|
},
|
||||||
"pi": {
|
"pi": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"bin.mjs",
|
||||||
"dist/**/*.js",
|
"dist/**/*.js",
|
||||||
"dist/**/*.d.ts",
|
"dist/**/*.d.ts",
|
||||||
"dist/**/*.d.ts.map",
|
"dist/**/*.d.ts.map",
|
||||||
|
|||||||
85
packages/cli/src/__tests__/bin-targets.test.ts
Normal file
85
packages/cli/src/__tests__/bin-targets.test.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { dirname, join, normalize } from "node:path";
|
||||||
|
import fg from "fast-glob";
|
||||||
|
import { parse } from "yaml";
|
||||||
|
|
||||||
|
const cliRoot = join(__dirname, "..", "..");
|
||||||
|
const workspaceRoot = join(cliRoot, "..", "..");
|
||||||
|
|
||||||
|
type PackageManifest = {
|
||||||
|
name?: string;
|
||||||
|
bin?: string | Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WorkspacePackage = {
|
||||||
|
dir: string;
|
||||||
|
manifestPath: string;
|
||||||
|
manifest: PackageManifest;
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadWorkspacePatterns(): string[] {
|
||||||
|
const workspaceManifestPath = join(workspaceRoot, "pnpm-workspace.yaml");
|
||||||
|
const workspaceManifest = parse(readFileSync(workspaceManifestPath, "utf-8")) as {
|
||||||
|
packages?: string[];
|
||||||
|
};
|
||||||
|
return workspaceManifest.packages ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function listWorkspacePackages(): WorkspacePackage[] {
|
||||||
|
const packageJsonPaths = fg
|
||||||
|
.sync(loadWorkspacePatterns().map((pattern) => `${pattern}/package.json`), {
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
absolute: true,
|
||||||
|
onlyFiles: true,
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
return packageJsonPaths.map((manifestPath) => ({
|
||||||
|
dir: dirname(manifestPath),
|
||||||
|
manifestPath,
|
||||||
|
manifest: JSON.parse(readFileSync(manifestPath, "utf-8")) as PackageManifest,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function listBins(manifest: PackageManifest): Array<[string, string]> {
|
||||||
|
if (!manifest.bin) return [];
|
||||||
|
if (typeof manifest.bin === "string") {
|
||||||
|
const fallbackName = manifest.name ?? "<anonymous-bin>";
|
||||||
|
return [[fallbackName, manifest.bin]];
|
||||||
|
}
|
||||||
|
return Object.entries(manifest.bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("workspace bin targets", () => {
|
||||||
|
const packagesWithBins = listWorkspacePackages().filter((pkg) => listBins(pkg.manifest).length > 0);
|
||||||
|
|
||||||
|
it("covers all workspace packages that declare bins", () => {
|
||||||
|
const packageNames = packagesWithBins.map((pkg) => pkg.manifest.name).sort();
|
||||||
|
expect(packageNames).toEqual([
|
||||||
|
"@runfusion/fusion",
|
||||||
|
"runfusion.ai",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
packagesWithBins.flatMap((pkg) =>
|
||||||
|
listBins(pkg.manifest).map(([binName, target]) => ({
|
||||||
|
packageName: pkg.manifest.name ?? pkg.manifestPath,
|
||||||
|
packageDir: pkg.dir,
|
||||||
|
binName,
|
||||||
|
target,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
'$packageName bin "$binName" points at a committed non-dist file',
|
||||||
|
({ packageDir, target }) => {
|
||||||
|
const normalizedTarget = normalize(target).replace(/^\.([/\\])/, "");
|
||||||
|
const resolvedTarget = join(packageDir, normalizedTarget);
|
||||||
|
|
||||||
|
expect(normalizedTarget).not.toMatch(/^dist(?:[/\\]|$)/);
|
||||||
|
expect(existsSync(resolvedTarget)).toBe(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -36,14 +36,16 @@ describe("CLI package.json publishing config", () => {
|
|||||||
const pkg = loadPackageJson("cli");
|
const pkg = loadPackageJson("cli");
|
||||||
const prepackScript = loadCliPrepackScript();
|
const prepackScript = loadCliPrepackScript();
|
||||||
|
|
||||||
it('has "bin" field with fn pointing to ./dist/bin.js', () => {
|
it('has "bin" field with fn/fusion pointing to committed launcher', () => {
|
||||||
expect(pkg.bin).toBeDefined();
|
expect(pkg.bin).toBeDefined();
|
||||||
expect(pkg.bin.fn).toBe("./dist/bin.js");
|
expect(pkg.bin.fn).toBe("./bin.mjs");
|
||||||
|
expect(pkg.bin.fusion).toBe("./bin.mjs");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has "files" array with refined globs for dist output', () => {
|
it('has "files" array with committed launcher and refined globs for dist output', () => {
|
||||||
expect(pkg.files).toBeDefined();
|
expect(pkg.files).toBeDefined();
|
||||||
expect(Array.isArray(pkg.files)).toBe(true);
|
expect(Array.isArray(pkg.files)).toBe(true);
|
||||||
|
expect(pkg.files).toContain("bin.mjs");
|
||||||
expect(pkg.files).toContain("dist/**/*.js");
|
expect(pkg.files).toContain("dist/**/*.js");
|
||||||
expect(pkg.files).toContain("dist/**/*.d.ts");
|
expect(pkg.files).toContain("dist/**/*.d.ts");
|
||||||
expect(pkg.files).toContain("dist/**/*.d.ts.map");
|
expect(pkg.files).toContain("dist/**/*.d.ts.map");
|
||||||
|
|||||||
Reference in New Issue
Block a user