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
8 changes: 8 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
"dist"
],
"type": "module",
"publishConfig": {
"executableFiles": [
"dist/resource-monitor/darwin-arm64/t3-resource-monitor",
"dist/resource-monitor/darwin-x64/t3-resource-monitor",
"dist/resource-monitor/linux-x64/t3-resource-monitor",
"dist/resource-monitor/win32-x64/t3-resource-monitor.exe"
]
},
"scripts": {
"dev": "node --watch src/bin.ts",
"build:bundle": "vp pack && vp pack src/service-launcher.ts --out-dir dist --no-clean",
Expand Down
41 changes: 6 additions & 35 deletions apps/server/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
resolveWebAssetBrandForPackageVersion,
resolveWebIconOverrides,
} from "../../../scripts/lib/brand-assets.ts";
import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts";
import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";
import { fromYaml } from "@t3tools/shared/schemaYaml";
import { resolveSpawnCommand } from "@t3tools/shared/shell";
Expand All @@ -28,22 +27,7 @@ import {
ServerCliPublishIconSourceMissingError,
ServerCliPublishIconTargetMissingError,
} from "./cliErrors.ts";

interface PackageJson {
name: string;
repository: {
type: string;
url: string;
directory: string;
};
bin: Record<string, string>;
type: string;
version: string;
engines: Record<string, string>;
files: string[];
dependencies: Record<string, string>;
overrides: Record<string, string>;
}
import { createServerCliPublishPackageJson } from "./cliManifest.ts";

const PackageJsonPrettyJson = fromJsonStringPretty(Schema.Unknown);
const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson);
Expand Down Expand Up @@ -241,25 +225,12 @@ const publishCmd = Command.make(
const workspaceConfig = yield* readWorkspaceConfig();
const workspaceCatalog = workspaceConfig.catalog ?? {};
const workspaceOverrides = workspaceConfig.overrides ?? {};
const pkg: PackageJson = {
name: serverPackageJson.name,
repository: serverPackageJson.repository,
bin: serverPackageJson.bin,
type: serverPackageJson.type,
const pkg = createServerCliPublishPackageJson({
source: serverPackageJson,
version,
engines: serverPackageJson.engines,
files: serverPackageJson.files,
dependencies: resolveCatalogDependencies(
serverPackageJson.dependencies,
workspaceCatalog,
"apps/server",
),
overrides: resolveCatalogDependencies(
workspaceOverrides,
workspaceCatalog,
"apps/server",
),
};
workspaceCatalog,
workspaceOverrides,
});

return {
packageJsonString: yield* encodePackageJson(pkg),
Expand Down
126 changes: 126 additions & 0 deletions apps/server/scripts/cliManifest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as NodeProcess from "node:process";
import * as NodeZlib from "node:zlib";

import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";

import serverPackageJson from "../package.json" with { type: "json" };
import { createServerCliPublishPackageJson } from "./cliManifest.ts";

const RESOURCE_MONITOR_EXECUTABLES = [
"dist/resource-monitor/darwin-arm64/t3-resource-monitor",
"dist/resource-monitor/darwin-x64/t3-resource-monitor",
"dist/resource-monitor/linux-x64/t3-resource-monitor",
"dist/resource-monitor/win32-x64/t3-resource-monitor.exe",
];
const encodeUnknownJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown));

function readTarField(header: Uint8Array, start: number, end: number): string {
const field = header.subarray(start, end);
const nullOffset = field.indexOf(0);
return field
.subarray(0, nullOffset === -1 ? field.length : nullOffset)
.toString()
.trim();
}

function readTarModes(tarball: Uint8Array): ReadonlyMap<string, number> {
const archive = NodeZlib.gunzipSync(tarball);
const modes = new Map<string, number>();

for (let offset = 0; offset + 512 <= archive.length; ) {
const header = archive.subarray(offset, offset + 512);
if (header.every((byte) => byte === 0)) break;

const name = readTarField(header, 0, 100);
const modeText = readTarField(header, 100, 108);
const sizeText = readTarField(header, 124, 136);
const mode = Number.parseInt(modeText, 8);
const size = Number.parseInt(sizeText, 8);

modes.set(name, mode & 0o777);
offset += 512 + Math.ceil(size / 512) * 512;
}

return modes;
}

describe("CLI publish manifest", () => {
it("marks every packaged resource monitor as executable", () => {
assert.deepStrictEqual(
serverPackageJson.publishConfig.executableFiles,
RESOURCE_MONITOR_EXECUTABLES,
);
});

it("retains executable file metadata in the generated publish manifest", () => {
const manifest = createServerCliPublishPackageJson({
source: { ...serverPackageJson, dependencies: {} },
version: "1.2.3",
workspaceCatalog: {},
workspaceOverrides: {},
});

assert.deepStrictEqual(manifest.publishConfig.executableFiles, RESOURCE_MONITOR_EXECUTABLES);
});

it.effect("packs every resource monitor with executable mode", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const fixtureDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-resource-monitor-pack-",
});
const outputDir = path.join(fixtureDir, "packed");

yield* fileSystem.makeDirectory(outputDir);
yield* fileSystem.writeFileString(
path.join(fixtureDir, "package.json"),
encodeUnknownJson({
name: "t3-resource-monitor-pack-fixture",
version: "1.0.0",
packageManager: "pnpm@11.10.0",
files: ["dist"],
publishConfig: serverPackageJson.publishConfig,
}),
);

for (const executable of RESOURCE_MONITOR_EXECUTABLES) {
const executablePath = path.join(fixtureDir, executable);
yield* fileSystem.makeDirectory(path.dirname(executablePath), { recursive: true });
yield* fileSystem.writeFileString(executablePath, "fixture\n");
yield* fileSystem.chmod(executablePath, 0o644);
}

const vpExecutable = path.resolve(
import.meta.dirname,
"../../../node_modules/vite-plus/bin/vp",
);
const child = yield* spawner.spawn(
ChildProcess.make(
NodeProcess.execPath,
[vpExecutable, "pm", "pack", "--pack-destination", outputDir],
{
cwd: fixtureDir,
stdout: "inherit",
stderr: "inherit",
},
),
);
assert.equal(yield* child.exitCode, 0);

const [tarballName] = yield* fileSystem.readDirectory(outputDir);
assert.ok(tarballName);
const modes = readTarModes(yield* fileSystem.readFile(path.join(outputDir, tarballName)));
for (const executable of RESOURCE_MONITOR_EXECUTABLES) {
assert.equal(modes.get(`package/${executable}`), 0o755, executable);
}
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);
});
50 changes: 50 additions & 0 deletions apps/server/scripts/cliManifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts";

export interface ServerCliPublishPackageJson {
readonly name: string;
readonly repository: {
readonly type: string;
readonly url: string;
readonly directory: string;
};
readonly bin: Readonly<Record<string, string>>;
readonly type: string;
readonly version: string;
readonly engines: Readonly<Record<string, string>>;
readonly files: ReadonlyArray<string>;
readonly dependencies: Readonly<Record<string, string>>;
readonly overrides: Readonly<Record<string, string>>;
readonly publishConfig: {
readonly executableFiles: ReadonlyArray<string>;
};
}

type ServerCliPackageSource = Omit<ServerCliPublishPackageJson, "version" | "overrides">;

export function createServerCliPublishPackageJson(input: {
readonly source: ServerCliPackageSource;
readonly version: string;
readonly workspaceCatalog: Record<string, string>;
readonly workspaceOverrides: Record<string, string>;
}): ServerCliPublishPackageJson {
return {
name: input.source.name,
repository: input.source.repository,
bin: input.source.bin,
type: input.source.type,
version: input.version,
engines: input.source.engines,
files: input.source.files,
dependencies: resolveCatalogDependencies(
input.source.dependencies,
input.workspaceCatalog,
"apps/server",
),
overrides: resolveCatalogDependencies(
input.workspaceOverrides,
input.workspaceCatalog,
"apps/server",
),
publishConfig: input.source.publishConfig,
};
}
Loading