diff --git a/test/commands/ui-bundle/dev.nut.ts b/test/commands/ui-bundle/dev.nut.ts index 721d054..d0fd1c2 100644 --- a/test/commands/ui-bundle/dev.nut.ts +++ b/test/commands/ui-bundle/dev.nut.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { execSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; @@ -25,11 +24,10 @@ import { createProjectWithMultipleUiBundles, createEmptyUiBundlesDir, createUiBundleDirWithoutMeta, + createUiBundle, writeManifest, uiBundlePath, - ensureSfCli, authOrgViaUrl, - REAL_HOME, } from './helpers/uiBundleProjectUtils.js'; /* ------------------------------------------------------------------ * @@ -83,7 +81,6 @@ describe('ui-bundle dev NUTs — Tier 2 CLI validation', () => { } session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); - ensureSfCli(); targetOrg = authOrgViaUrl(); }); @@ -121,11 +118,7 @@ describe('ui-bundle dev NUTs — Tier 2 CLI validation', () => { // Discovery treats this as ambiguous intent and rejects it. it('should error on --name conflict when inside a different uiBundle', () => { const projectDir = createProjectWithUiBundle(session, 'nameConflict', 'appA'); - execSync('sf ui-bundle generate --name appB', { - cwd: projectDir, - stdio: 'pipe', - env: { ...process.env, HOME: REAL_HOME, USERPROFILE: REAL_HOME }, - }); + createUiBundle(projectDir, 'appB'); const cwdInsideAppA = uiBundlePath(projectDir, 'appA'); diff --git a/test/commands/ui-bundle/devPort.nut.ts b/test/commands/ui-bundle/devPort.nut.ts index 163ccde..b2736b6 100644 --- a/test/commands/ui-bundle/devPort.nut.ts +++ b/test/commands/ui-bundle/devPort.nut.ts @@ -17,7 +17,7 @@ import type { Server } from 'node:net'; import { TestSession } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; -import { createProjectWithDevServer, ensureSfCli, authOrgViaUrl } from './helpers/uiBundleProjectUtils.js'; +import { createProjectWithDevServer, authOrgViaUrl } from './helpers/uiBundleProjectUtils.js'; import { occupyPort, spawnUiBundleDev, @@ -58,7 +58,6 @@ describe('ui-bundle dev NUTs — Tier 2 port handling', function () { } session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); - ensureSfCli(); targetOrg = authOrgViaUrl(); }); diff --git a/test/commands/ui-bundle/devWithUrl.nut.ts b/test/commands/ui-bundle/devWithUrl.nut.ts index 70e71a5..64a8833 100644 --- a/test/commands/ui-bundle/devWithUrl.nut.ts +++ b/test/commands/ui-bundle/devWithUrl.nut.ts @@ -21,7 +21,6 @@ import { createProjectWithDevServer, createProjectWithUiBundle, writeManifest, - ensureSfCli, authOrgViaUrl, } from './helpers/uiBundleProjectUtils.js'; import { @@ -66,7 +65,6 @@ describe('ui-bundle dev NUTs — Tier 2 URL/proxy integration', function () { } session = await TestSession.create({ devhubAuthStrategy: 'NONE' }); - ensureSfCli(); targetOrg = authOrgViaUrl(); }); diff --git a/test/commands/ui-bundle/helpers/uiBundleProjectUtils.ts b/test/commands/ui-bundle/helpers/uiBundleProjectUtils.ts index 8423f1a..0ae41db 100644 --- a/test/commands/ui-bundle/helpers/uiBundleProjectUtils.ts +++ b/test/commands/ui-bundle/helpers/uiBundleProjectUtils.ts @@ -16,17 +16,14 @@ import { execSync } from 'node:child_process'; import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; -import { homedir, tmpdir } from 'node:os'; +import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { TestSession } from '@salesforce/cli-plugins-testkit'; import { UI_BUNDLES_FOLDER } from '../../../../src/config/uiBundleDiscovery.js'; -/** - * Real home directory captured at module load, before TestSession overrides process.env.HOME. - * Used when running `sf ui-bundle generate` so the CLI finds linked plugin-templates - * (TestSession sets HOME to a temp dir, which hides linked plugins). - */ -export const REAL_HOME = homedir(); +const DEFAULT_SFDX_PROJECT = { + packageDirectories: [{ path: 'force-app', default: true }], +}; /** * Relative path from project root to the uiBundles folder. @@ -41,22 +38,6 @@ export function uiBundlePath(projectDir: string, uiBundleName?: string): string return uiBundleName ? join(projectDir, UI_BUNDLES_PATH, uiBundleName) : join(projectDir, UI_BUNDLES_PATH); } -/** - * Verify the global `sf` CLI is available and has the required commands. - * Must be called after TestSession.create() since the session sets a valid HOME. - */ -export function ensureSfCli(): void { - try { - execSync('sf project generate --help', { stdio: 'pipe', timeout: 30_000 }); - } catch { - throw new Error( - 'Global sf CLI with plugin-templates not found.\n' + - 'Install: npm install @salesforce/cli -g\n' + - 'CI installs @salesforce/cli@nightly via nut.yml.' - ); - } -} - /** * Authenticate an org via TESTKIT_AUTH_URL without requiring DevHub. * Returns the authenticated username. @@ -70,7 +51,6 @@ export function authOrgViaUrl(): string { throw new Error('TESTKIT_AUTH_URL environment variable is not set.'); } - // Use --sfdx-url-file for cross-platform reliability const tmpFile = join(tmpdir(), `testkit-auth-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`); try { writeFileSync(tmpFile, authUrl, 'utf8'); @@ -86,28 +66,32 @@ export function authOrgViaUrl(): string { } /** - * Run `sf project generate --name ` inside the session directory. - * Returns the absolute path to the generated project root. + * Create a minimal SFDX project directory with sfdx-project.json. + * Returns the absolute path to the project root. */ export function createProject(session: TestSession, name: string): string { - execSync(`sf project generate --name ${name}`, { - cwd: session.dir, - stdio: 'pipe', - }); - return join(session.dir, name); + const projectDir = join(session.dir, name); + mkdirSync(projectDir, { recursive: true }); + writeFileSync(join(projectDir, 'sfdx-project.json'), JSON.stringify(DEFAULT_SFDX_PROJECT, null, 2)); + return projectDir; } /** - * Run `sf project generate` then `sf ui-bundle generate --name ` inside - * the project. Returns the absolute path to the generated project root. + * Create a uiBundle directory with the required .uibundle-meta.xml file. + */ +export function createUiBundle(projectDir: string, name: string): void { + const dir = uiBundlePath(projectDir, name); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, `${name}.uibundle-meta.xml`), ''); +} + +/** + * Create a uiBundle directory with the required .uibundle-meta.xml file inside + * a project. Returns the absolute path to the project root. */ export function createProjectWithUiBundle(session: TestSession, projectName: string, uiBundleName: string): string { const projectDir = createProject(session, projectName); - execSync(`sf ui-bundle generate --name ${uiBundleName}`, { - cwd: projectDir, - stdio: 'pipe', - env: { ...process.env, HOME: REAL_HOME, USERPROFILE: REAL_HOME }, - }); + createUiBundle(projectDir, uiBundleName); return projectDir; } @@ -122,11 +106,7 @@ export function createProjectWithMultipleUiBundles( ): string { const projectDir = createProject(session, projectName); for (const name of uiBundleNames) { - execSync(`sf ui-bundle generate --name ${name}`, { - cwd: projectDir, - stdio: 'pipe', - env: { ...process.env, HOME: REAL_HOME, USERPROFILE: REAL_HOME }, - }); + createUiBundle(projectDir, name); } return projectDir; } @@ -160,7 +140,7 @@ export function writeManifest(projectDir: string, uiBundleName: string, manifest * * The script is CommonJS (.cjs) to avoid ESM/shell quoting issues. */ -export function createDevServerScript(uiBundleDir: string, port: number): string { +function createDevServerScript(uiBundleDir: string, port: number): string { const script = [ "const http = require('http');", 'const server = http.createServer((_, res) => {',