From a5ce34771f7ed7758e69754ba879ba91f08ca410 Mon Sep 17 00:00:00 2001 From: aysko Date: Tue, 26 May 2026 16:28:39 +0100 Subject: [PATCH 1/2] Fix Windows path normalization in target plans --- packages/targets/browser-safari/src/index.ts | 24 +++++++++++++++----- packages/targets/pkg-jsr/src/index.ts | 9 ++++++-- packages/targets/plugin-vscode/src/index.ts | 12 +++++++--- packages/targets/tv-roku/src/index.ts | 4 ++-- packages/targets/web-static/src/index.ts | 4 ++-- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/packages/targets/browser-safari/src/index.ts b/packages/targets/browser-safari/src/index.ts index 542f60bc..5617ee82 100644 --- a/packages/targets/browser-safari/src/index.ts +++ b/packages/targets/browser-safari/src/index.ts @@ -3,7 +3,7 @@ import { execFileSync, execSync } from 'node:child_process'; import { createSign } from 'node:crypto'; import { existsSync } from 'node:fs'; import { mkdir, writeFile } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; +import { join, posix, resolve } from 'node:path'; interface Config { bundleId: string; // e.g. "com.example.MyApp.Extension" @@ -65,15 +65,27 @@ function planPath(outDir: string, bundleId: string, version: string): string { return join(outDir, `${safeFileStem(bundleId)}-${safeFileStem(version)}.safari-plan.json`); } +function isWindowsPath(path: string): boolean { + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); +} + +function joinLike(base: string, ...parts: string[]): string { + return isWindowsPath(base) ? join(base, ...parts) : posix.join(base, ...parts); +} + +function resolveLike(base: string, path: string): string { + return isWindowsPath(base) ? resolve(base, path) : posix.resolve(base, path); +} + function buildPlan( ctx: { projectDir: string; outDir: string; version: string }, config: Config, ): SafariPackagePlan { - const projectDir = resolve(ctx.projectDir, config.projectDir ?? '.'); + const projectDir = resolveLike(ctx.projectDir, config.projectDir ?? '.'); const scheme = config.scheme ?? 'App'; - const archivePath = join(ctx.outDir, `${safeFileStem(config.bundleId)}-${safeFileStem(ctx.version)}.xcarchive`); - const xcodeProj = join(projectDir, `${scheme}.xcodeproj`); - const xcWorkspace = join(projectDir, `${scheme}.xcworkspace`); + const archivePath = joinLike(ctx.outDir, `${safeFileStem(config.bundleId)}-${safeFileStem(ctx.version)}.xcarchive`); + const xcodeProj = joinLike(projectDir, `${scheme}.xcodeproj`); + const xcWorkspace = joinLike(projectDir, `${scheme}.xcworkspace`); const appName = config.bundleId.split('.').pop() ?? 'Extension'; const archiveArgs = [ existsSync(xcWorkspace) ? '-workspace' : '-project', @@ -97,7 +109,7 @@ function buildPlan( command: 'xcrun', args: [ 'safari-web-extension-converter', - join(projectDir, 'dist'), + joinLike(projectDir, 'dist'), '--app-name', appName, '--bundle-identifier', diff --git a/packages/targets/pkg-jsr/src/index.ts b/packages/targets/pkg-jsr/src/index.ts index facc7210..a17616cf 100644 --- a/packages/targets/pkg-jsr/src/index.ts +++ b/packages/targets/pkg-jsr/src/index.ts @@ -1,5 +1,5 @@ import { defineTarget, exec, manualSetup } from '@profullstack/sh1pt-core'; -import { join } from 'node:path'; +import { join, posix } from 'node:path'; // JSR (jsr.io) - TS-native registry. Publishes source TS directly; the // registry handles transpilation for Node/Deno/Bun consumers. Scoped packages @@ -11,7 +11,12 @@ interface Config { } function packagePath(ctx: { projectDir: string }, config: Config): string { - return config.packageDir ? join(ctx.projectDir, config.packageDir) : ctx.projectDir; + if (!config.packageDir) return ctx.projectDir; + return isWindowsPath(ctx.projectDir) ? join(ctx.projectDir, config.packageDir) : posix.join(ctx.projectDir, config.packageDir); +} + +function isWindowsPath(path: string): boolean { + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); } function packageId(config: Config, version: string): string { diff --git a/packages/targets/plugin-vscode/src/index.ts b/packages/targets/plugin-vscode/src/index.ts index 485992bf..91aca8fe 100644 --- a/packages/targets/plugin-vscode/src/index.ts +++ b/packages/targets/plugin-vscode/src/index.ts @@ -1,6 +1,6 @@ import { defineTarget, setupGuide, exec } from '@profullstack/sh1pt-core'; import { mkdir, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; +import { join, posix } from 'node:path'; interface Config { publisher: string; // e.g. "mycompany" @@ -10,11 +10,17 @@ interface Config { } function packageDir(ctx: { projectDir: string }, config: Config): string { - return config.packageDir ? join(ctx.projectDir, config.packageDir) : ctx.projectDir; + if (!config.packageDir) return ctx.projectDir; + return isWindowsPath(ctx.projectDir) ? join(ctx.projectDir, config.packageDir) : posix.join(ctx.projectDir, config.packageDir); } function packageArtifact(ctx: { outDir: string; version: string }, config: Config): string { - return join(ctx.outDir, `${config.extensionName}-${ctx.version}.vsix`); + const file = `${config.extensionName}-${ctx.version}.vsix`; + return isWindowsPath(ctx.outDir) ? join(ctx.outDir, file) : posix.join(ctx.outDir, file); +} + +function isWindowsPath(path: string): boolean { + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); } function packageArgs(ctx: { outDir: string }, config: Config): string[] { diff --git a/packages/targets/tv-roku/src/index.ts b/packages/targets/tv-roku/src/index.ts index 28e109ff..5112b141 100644 --- a/packages/targets/tv-roku/src/index.ts +++ b/packages/targets/tv-roku/src/index.ts @@ -1,6 +1,6 @@ import { defineTarget, manualSetup } from '@profullstack/sh1pt-core'; import { access, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises'; -import { join, relative, resolve } from 'node:path'; +import { join, relative, resolve, sep } from 'node:path'; // Roku apps run on BrightScript + SceneGraph. Unlike tvOS / Android TV / // Fire TV, there is no supported React runtime on Roku OS — react-tv is @@ -42,7 +42,7 @@ async function listFiles(root: string, dir = root): Promise { const files = await Promise.all(entries.map(async (entry) => { const path = join(dir, entry.name); if (entry.isDirectory()) return listFiles(root, path); - if (entry.isFile()) return [relative(root, path)]; + if (entry.isFile()) return [relative(root, path).split(sep).join('/')]; return []; })); return files.flat().sort(); diff --git a/packages/targets/web-static/src/index.ts b/packages/targets/web-static/src/index.ts index 1405c047..7fb58bed 100644 --- a/packages/targets/web-static/src/index.ts +++ b/packages/targets/web-static/src/index.ts @@ -1,6 +1,6 @@ import { defineTarget, manualSetup } from '@profullstack/sh1pt-core'; import { cp, mkdir, readdir, stat, writeFile } from 'node:fs/promises'; -import { join, relative, resolve } from 'node:path'; +import { join, relative, resolve, sep } from 'node:path'; interface Config { dir: string; // built output directory @@ -14,7 +14,7 @@ async function listFiles(root: string, dir = root): Promise { const files = await Promise.all(entries.map(async (entry) => { const path = join(dir, entry.name); if (entry.isDirectory()) return listFiles(root, path); - if (entry.isFile()) return [relative(root, path)]; + if (entry.isFile()) return [relative(root, path).split(sep).join('/')]; return []; })); return files.flat().sort(); From a54d0ea7a84cec238d62fa69a3a8b7a27f366078 Mon Sep 17 00:00:00 2001 From: aysko Date: Tue, 26 May 2026 16:35:04 +0100 Subject: [PATCH 2/2] Address path normalization review --- packages/targets/browser-safari/src/index.ts | 4 ++-- packages/targets/pkg-jsr/src/index.ts | 2 +- packages/targets/plugin-vscode/src/index.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/targets/browser-safari/src/index.ts b/packages/targets/browser-safari/src/index.ts index 5617ee82..d47b0039 100644 --- a/packages/targets/browser-safari/src/index.ts +++ b/packages/targets/browser-safari/src/index.ts @@ -62,11 +62,11 @@ function safeFileStem(value: string): string { } function planPath(outDir: string, bundleId: string, version: string): string { - return join(outDir, `${safeFileStem(bundleId)}-${safeFileStem(version)}.safari-plan.json`); + return joinLike(outDir, `${safeFileStem(bundleId)}-${safeFileStem(version)}.safari-plan.json`); } function isWindowsPath(path: string): boolean { - return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')); } function joinLike(base: string, ...parts: string[]): string { diff --git a/packages/targets/pkg-jsr/src/index.ts b/packages/targets/pkg-jsr/src/index.ts index a17616cf..7d8845f8 100644 --- a/packages/targets/pkg-jsr/src/index.ts +++ b/packages/targets/pkg-jsr/src/index.ts @@ -16,7 +16,7 @@ function packagePath(ctx: { projectDir: string }, config: Config): string { } function isWindowsPath(path: string): boolean { - return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')); } function packageId(config: Config, version: string): string { diff --git a/packages/targets/plugin-vscode/src/index.ts b/packages/targets/plugin-vscode/src/index.ts index 91aca8fe..b8ccde58 100644 --- a/packages/targets/plugin-vscode/src/index.ts +++ b/packages/targets/plugin-vscode/src/index.ts @@ -20,7 +20,11 @@ function packageArtifact(ctx: { outDir: string; version: string }, config: Confi } function isWindowsPath(path: string): boolean { - return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')) || path.startsWith('//'); + return path.includes('\\') || /^[A-Za-z]:\//.test(path.replace(/\\/g, '/')); +} + +function joinLike(base: string, ...parts: string[]): string { + return isWindowsPath(base) ? join(base, ...parts) : posix.join(base, ...parts); } function packageArgs(ctx: { outDir: string }, config: Config): string[] { @@ -48,7 +52,7 @@ export default defineTarget({ async build(ctx, config) { if (ctx.dryRun) { - const planPath = join(ctx.outDir, 'vscode-package.json'); + const planPath = joinLike(ctx.outDir, 'vscode-package.json'); ctx.log(`vsce: dry-run package plan for ${config.publisher}.${config.extensionName} v${ctx.version}`); await mkdir(ctx.outDir, { recursive: true }); await writeFile(planPath, renderPackagePlan(ctx, config), 'utf-8');