From cfb255ecec3012f5301c8261c0afa37f528468bc Mon Sep 17 00:00:00 2001 From: Eli Adelhult Date: Tue, 19 May 2026 09:30:28 +0200 Subject: [PATCH 1/2] Enforce ProjectPath being absolute --- src/commands/cache/command.ts | 5 +++-- src/commands/compile/command.ts | 3 +-- src/commands/compile/impl.ts | 11 ++++++++--- src/commands/gxgames/command.ts | 3 +-- src/commands/gxgames/commands/link-impl.ts | 7 ++++--- src/commands/gxgames/commands/meta-impl.ts | 7 ++++--- src/commands/gxgames/commands/publish-impl.ts | 7 ++++--- src/commands/gxgames/commands/upload-impl.ts | 7 ++++--- src/commands/package/command.ts | 3 +-- src/commands/package/impl.ts | 17 +++++++++++------ src/commands/resourcetool/command.ts | 11 +++++------ src/commands/resourcetool/impl.ts | 8 +++++--- src/commands/run/command.ts | 3 +-- src/commands/run/impl.ts | 11 ++++++++--- src/project.ts | 6 +++--- 15 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/commands/cache/command.ts b/src/commands/cache/command.ts index 49e34ef..09ef4fa 100644 --- a/src/commands/cache/command.ts +++ b/src/commands/cache/command.ts @@ -21,7 +21,7 @@ import { findProjectFile, parseProjectPath } from "~/project"; export const FLAGS = { project: { kind: "parsed", - parse: parseProjectPath, + parse: String, brief: "Path to the project .yyp file", optional: true, }, @@ -79,7 +79,8 @@ export async function setupCache( if (flags.cacheDir) { cacheType = { type: "absolute", path: flags.cacheDir }; } else if (flags.project) { - cacheType = { type: "infer", projectDir: ctx.path.dirname(flags.project) }; + const projectPath = parseProjectPath(ctx, flags.project); + cacheType = { type: "infer", projectDir: ctx.path.dirname(projectPath) }; } else { // Try inferring from cwd. const projectPath = await findProjectFile(ctx, ctx.process.cwd()); diff --git a/src/commands/compile/command.ts b/src/commands/compile/command.ts index 48ea58c..8e7d17f 100644 --- a/src/commands/compile/command.ts +++ b/src/commands/compile/command.ts @@ -16,7 +16,6 @@ import { buildCommand } from "@stricli/core"; import { TARGETS, TargetSchema } from "~/target"; -import { parseProjectPath } from "~/project"; import { parseToolchainVersion } from "~/toolchain"; export const compileProjectCommand = buildCommand({ @@ -28,7 +27,7 @@ export const compileProjectCommand = buildCommand({ { brief: "Path to the project .yyp file", placeholder: "project", - parse: parseProjectPath, + parse: String, optional: true, }, ], diff --git a/src/commands/compile/impl.ts b/src/commands/compile/impl.ts index 64d7ae7..ba681db 100644 --- a/src/commands/compile/impl.ts +++ b/src/commands/compile/impl.ts @@ -15,13 +15,18 @@ */ import type { Context } from "~/context"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { runBuildPipeline, type CommonCliBuildFlags } from "~/build-pipeline"; export default async function ( this: Context, flags: CommonCliBuildFlags, - project?: ProjectPath, + project?: string, ): Promise { - await runBuildPipeline(this, flags, project, { type: "compile" }); + await runBuildPipeline( + this, + flags, + project ? parseProjectPath(this, project) : undefined, + { type: "compile" }, + ); } diff --git a/src/commands/gxgames/command.ts b/src/commands/gxgames/command.ts index 94ca685..486dac3 100644 --- a/src/commands/gxgames/command.ts +++ b/src/commands/gxgames/command.ts @@ -15,12 +15,11 @@ */ import { buildCommand, buildRouteMap } from "@stricli/core"; -import { parseProjectPath } from "~/project"; const projectParam = { brief: "Path to the project .yyp file (defaults to current directory)", placeholder: "project", - parse: parseProjectPath, + parse: String, optional: true as const, }; diff --git a/src/commands/gxgames/commands/link-impl.ts b/src/commands/gxgames/commands/link-impl.ts index cbd4ec9..0a9ef6a 100644 --- a/src/commands/gxgames/commands/link-impl.ts +++ b/src/commands/gxgames/commands/link-impl.ts @@ -17,7 +17,7 @@ import * as p from "@clack/prompts"; import type { Context } from "~/context"; import { KnownError } from "~/error"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { createAuthManager } from "../auth"; import { apiUserErrorMessage, getApiClient } from "../api"; @@ -26,11 +26,12 @@ import { LinkStorage } from "../api"; export default async function ( this: Context, flags: { studioid?: string; gameid?: string }, - project?: ProjectPath, + project?: string, ): Promise { let studioId = flags.studioid; let gameId = flags.gameid; - const projectDir = project ? this.path.dirname(project) : undefined; + const projectPath = project ? parseProjectPath(this, project) : undefined; + const projectDir = projectPath ? this.path.dirname(projectPath) : undefined; if (!studioId || !gameId) { const api = getApiClient(this, createAuthManager(this, projectDir)); diff --git a/src/commands/gxgames/commands/meta-impl.ts b/src/commands/gxgames/commands/meta-impl.ts index fecaf7e..2f3d4d2 100644 --- a/src/commands/gxgames/commands/meta-impl.ts +++ b/src/commands/gxgames/commands/meta-impl.ts @@ -16,7 +16,7 @@ import type { Context } from "~/context"; import { KnownError } from "~/error"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { apiUserErrorMessage, LinkStorage } from "../api"; import { createAuthManager } from "../auth"; @@ -39,9 +39,10 @@ interface MetaFlags { export default async function ( this: Context, flags: MetaFlags, - project?: ProjectPath, + project?: string, ): Promise { - const projectDir = project ? this.path.dirname(project) : undefined; + const projectPath = project ? parseProjectPath(this, project) : undefined; + const projectDir = projectPath ? this.path.dirname(projectPath) : undefined; const link = await new LinkStorage(this, projectDir).read(); const api = getApiClient(this, createAuthManager(this, projectDir)); diff --git a/src/commands/gxgames/commands/publish-impl.ts b/src/commands/gxgames/commands/publish-impl.ts index 3f4b47b..85c9eaf 100644 --- a/src/commands/gxgames/commands/publish-impl.ts +++ b/src/commands/gxgames/commands/publish-impl.ts @@ -17,7 +17,7 @@ import type { Context } from "~/context"; import chalk from "chalk"; import { KnownError } from "~/error"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { apiUserErrorMessage, LinkStorage } from "../api"; import { createAuthManager } from "../auth"; @@ -26,9 +26,10 @@ import { getApiClient } from "../api"; export default async function ( this: Context, _flags: Record, - project?: ProjectPath, + project?: string, ): Promise { - const projectDir = project ? this.path.dirname(project) : undefined; + const projectPath = project ? parseProjectPath(this, project) : undefined; + const projectDir = projectPath ? this.path.dirname(projectPath) : undefined; const link = await new LinkStorage(this, projectDir).read(); const api = getApiClient(this, createAuthManager(this, projectDir)); diff --git a/src/commands/gxgames/commands/upload-impl.ts b/src/commands/gxgames/commands/upload-impl.ts index 685b30a..16ebc84 100644 --- a/src/commands/gxgames/commands/upload-impl.ts +++ b/src/commands/gxgames/commands/upload-impl.ts @@ -20,7 +20,7 @@ import chalk from "chalk"; import { apiUserErrorMessage, getApiClient } from "../api"; import { createAuthManager } from "../auth"; import { KnownError } from "~/error"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { LinkStorage } from "../api"; @@ -33,9 +33,10 @@ const validateVersion = (v: string | undefined): string | undefined => export default async function ( this: Context, flags: { file: string; version?: string }, - project?: ProjectPath, + project?: string, ): Promise { - const projectDir = project ? this.path.dirname(project) : undefined; + const projectPath = project ? parseProjectPath(this, project) : undefined; + const projectDir = projectPath ? this.path.dirname(projectPath) : undefined; const link = await new LinkStorage(this, projectDir).read(); const api = getApiClient(this, createAuthManager(this, projectDir)); diff --git a/src/commands/package/command.ts b/src/commands/package/command.ts index 0542d17..9ad7b5b 100644 --- a/src/commands/package/command.ts +++ b/src/commands/package/command.ts @@ -16,7 +16,6 @@ import { buildCommand } from "@stricli/core"; import { TARGETS, TargetSchema } from "~/target"; -import { parseProjectPath } from "~/project"; import { parseToolchainVersion } from "~/toolchain"; export const packageCommand = buildCommand({ @@ -28,7 +27,7 @@ export const packageCommand = buildCommand({ { brief: "Path to the project .yyp file", placeholder: "project", - parse: parseProjectPath, + parse: String, optional: true, }, ], diff --git a/src/commands/package/impl.ts b/src/commands/package/impl.ts index 7fcc5d2..6c0c7cd 100644 --- a/src/commands/package/impl.ts +++ b/src/commands/package/impl.ts @@ -15,16 +15,21 @@ */ import type { Context } from "~/context"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { runBuildPipeline, type CommonCliBuildFlags } from "~/build-pipeline"; export default async function ( this: Context, flags: CommonCliBuildFlags & { output?: string }, - project?: ProjectPath, + project?: string, ): Promise { - await runBuildPipeline(this, flags, project, { - type: "package", - outputPath: flags.output, - }); + await runBuildPipeline( + this, + flags, + project ? parseProjectPath(this, project) : undefined, + { + type: "package", + outputPath: flags.output, + }, + ); } diff --git a/src/commands/resourcetool/command.ts b/src/commands/resourcetool/command.ts index 8ecd1e0..09c5123 100644 --- a/src/commands/resourcetool/command.ts +++ b/src/commands/resourcetool/command.ts @@ -15,7 +15,6 @@ */ import { buildCommand, buildRouteMap } from "@stricli/core"; -import { parseProjectPath, type ProjectPath } from "~/project"; import type { Context } from "~/context"; import type { CommonFlags } from "./impl"; @@ -31,7 +30,7 @@ const commonFlags = { const projectParam = { brief: "Path to the project .yyp file", placeholder: "project", - parse: parseProjectPath, + parse: String, optional: true as const, }; @@ -44,7 +43,7 @@ export const resourcetoolCommand = buildRouteMap({ default: async function ( this: Context, flags: CommonFlags, - project?: ProjectPath, + project?: string, ) { return run(this, flags, project, { mode: "mcp" }); }, @@ -64,7 +63,7 @@ export const resourcetoolCommand = buildRouteMap({ this: Context, flags: CommonFlags, command: string, - project?: ProjectPath, + project?: string, ) { return run(this, flags, project, { mode: "command", command }); }, @@ -93,7 +92,7 @@ export const resourcetoolCommand = buildRouteMap({ default: async function ( this: Context, flags: CommonFlags, - project?: ProjectPath, + project?: string, ) { return run(this, flags, project, { mode: "cli" }); }, @@ -113,7 +112,7 @@ export const resourcetoolCommand = buildRouteMap({ this: Context, flags: CommonFlags, file: string, - project?: ProjectPath, + project?: string, ) { return run(this, flags, project, { mode: "script", diff --git a/src/commands/resourcetool/impl.ts b/src/commands/resourcetool/impl.ts index ef27294..d034055 100644 --- a/src/commands/resourcetool/impl.ts +++ b/src/commands/resourcetool/impl.ts @@ -16,7 +16,7 @@ import { Cache } from "~/cache"; import { type Context } from "~/context"; -import { findProjectFile, type ProjectPath } from "~/project"; +import { findProjectFile, parseProjectPath } from "~/project"; import { downloadGmpm, downloadPackageTool, @@ -34,11 +34,13 @@ export interface CommonFlags { export async function run( ctx: Context, flags: CommonFlags, - project: ProjectPath | undefined, + project: string | undefined, mode: ResourceToolMode, ): Promise { const cwd = ctx.process.cwd(); - const projectPath = project ?? (await findProjectFile(ctx, cwd)); + const projectPath = project + ? parseProjectPath(ctx, project) + : await findProjectFile(ctx, cwd); if (projectPath === undefined && mode.mode === "mcp") { // Since we hide the "project load" tool in mcp mode diff --git a/src/commands/run/command.ts b/src/commands/run/command.ts index c5305d3..1be8e9e 100644 --- a/src/commands/run/command.ts +++ b/src/commands/run/command.ts @@ -16,7 +16,6 @@ import { buildCommand } from "@stricli/core"; import { TARGETS, TargetSchema } from "~/target"; -import { parseProjectPath } from "~/project"; import { parseToolchainVersion } from "~/toolchain"; export const runCommand = buildCommand({ @@ -28,7 +27,7 @@ export const runCommand = buildCommand({ { brief: "Path to the project .yyp file", placeholder: "project", - parse: parseProjectPath, + parse: String, optional: true, }, ], diff --git a/src/commands/run/impl.ts b/src/commands/run/impl.ts index 28baa95..253b8ac 100644 --- a/src/commands/run/impl.ts +++ b/src/commands/run/impl.ts @@ -15,13 +15,18 @@ */ import type { Context } from "~/context"; -import type { ProjectPath } from "~/project"; +import { parseProjectPath } from "~/project"; import { runBuildPipeline, type CommonCliBuildFlags } from "~/build-pipeline"; export default async function ( this: Context, flags: CommonCliBuildFlags, - project?: ProjectPath, + project?: string, ): Promise { - await runBuildPipeline(this, flags, project, { type: "run" }); + await runBuildPipeline( + this, + flags, + project ? parseProjectPath(this, project) : undefined, + { type: "run" }, + ); } diff --git a/src/project.ts b/src/project.ts index 668631c..dae4a5d 100644 --- a/src/project.ts +++ b/src/project.ts @@ -19,11 +19,11 @@ import { KnownError } from "./error"; export type ProjectPath = string & { readonly __brand: unique symbol }; -export function parseProjectPath(s: string): ProjectPath { +export function parseProjectPath(ctx: Context, s: string): ProjectPath { if (!s.endsWith(".yyp")) { throw new KnownError(`Expected a file with the .yyp extension.`); } - return s as ProjectPath; + return ctx.path.resolve(s) as ProjectPath; } export async function findProjectFile( @@ -35,7 +35,7 @@ export async function findProjectFile( if (!yypFile) { return undefined; } - return ctx.path.join(dir, yypFile) as ProjectPath; + return ctx.path.resolve(ctx.path.join(dir, yypFile)) as ProjectPath; } export function getProjectName(ctx: Context, projectPath: ProjectPath) { From 81561ed8622065e4fa186dcda927c94ba4b0bfe8 Mon Sep 17 00:00:00 2001 From: Eli Adelhult Date: Tue, 19 May 2026 09:35:07 +0200 Subject: [PATCH 2/2] 1.4.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af100a2..802b2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.4.1 + +- Fix: Relative project paths are now handled correctly (e.g. `gm-cli ./some/project.yyp` works as intended) + # 1.4.0 - Fix: `gm-cli package` now works correctly on Linux. diff --git a/package.json b/package.json index 99fd5f4..66b320e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/YoYoGames/gm-cli" }, "type": "module", - "version": "1.4.0", + "version": "1.4.1", "files": [ "dist", "NOTICE"