diff --git a/.changeset/modern-pugs-appear.md b/.changeset/modern-pugs-appear.md index c9b9f56e89..8b502fb52d 100644 --- a/.changeset/modern-pugs-appear.md +++ b/.changeset/modern-pugs-appear.md @@ -2,7 +2,7 @@ '@backstage/cli': patch --- -Added a new `--entry-dir` option to the `package start` command, which allows you to specify a custom entry directory for development applications. This is particularly useful when maintaining separate dev apps for different versions of your plugin (e.g., stable and alpha). +Added a new `--entrypoint` option to the `package start` command, which allows you to specify a custom entry directory/file for development applications. This is particularly useful when maintaining separate dev apps for different versions of your plugin (e.g., stable and alpha). **Example usage:** @@ -15,7 +15,5 @@ dev/ index.ts ``` -- The default `yarn package start` command uses the `dev/` folder as the entry point and executes `dev/index.tsx` -- Running `yarn package start --entry-dir dev/alpha` will instead use `dev/alpha/` as the entry point and execute `dev/alpha/index.ts` - -This enables you to maintain multiple development environments within a single plugin package. +- The default `yarn package start` command uses the `dev/` folder as the entry point and executes `dev/index.tsx` file; +- Running `yarn package start --entrypoint dev/alpha` will instead use `dev/alpha/` as the entry point and execute `dev/alpha/index.ts` file. diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index da8a9650f1..ce8f862c98 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -177,12 +177,12 @@ Usage: backstage-cli package start [options] Start a package for local development Options: - --config Config files to load instead of app-config.yaml (default: []) - --role Run the command with an explicit package role - --check Enable type checking and linting if available - --inspect Enable debugger in Node.js environments - --inspect-brk Enable debugger in Node.js environments, breaking before code starts - --entry-dir Path to the directory containing the entry point file + --config Config files to load instead of app-config.yaml (default: []) + --role Run the command with an explicit package role + --check Enable type checking and linting if available + --inspect Enable debugger in Node.js environments + --inspect-brk Enable debugger in Node.js environments, breaking before code starts + --entrypoint Entry directory path (uses index file) or entry file path (without extension). Defaults to "dev" ``` ## package build diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index e2f5bb9cc6..a8df5242ad 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -312,6 +312,7 @@ Usage: program [options] Options: --check --config + --entrypoint --inspect [host] --inspect-brk [host] --link diff --git a/packages/cli/src/modules/build/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts index 4a65953f76..b38957f488 100644 --- a/packages/cli/src/modules/build/commands/package/start/command.ts +++ b/packages/cli/src/modules/build/commands/package/start/command.ts @@ -23,7 +23,7 @@ import { paths } from '../../../../../lib/paths'; export async function command(opts: OptionValues): Promise { await startPackage({ role: await findRoleFromCommand(opts), - entryDir: opts.entryDir, + entrypoint: opts.entrypoint, targetDir: paths.targetDir, configPaths: opts.config as string[], checksEnabled: Boolean(opts.check), diff --git a/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts new file mode 100644 index 0000000000..b2aa172867 --- /dev/null +++ b/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { resolveEntryPath } from './startPackage'; + +describe('resolveEntryPath', () => { + const mockDir = createMockDirectory(); + + afterEach(() => { + mockDir.clear(); + }); + + it('should remove file extensions', () => { + mockDir.setContent({ + 'dev/custom.tsx': '// dev app code', + }); + + const result = resolveEntryPath('dev/custom.tsx', mockDir.path); + + expect(result).toBe('dev/custom'); + }); + + it('should remove trailing slashes', () => { + mockDir.setContent({ + 'dev/alpha.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev/alpha/', mockDir.path); + + expect(result).toBe('dev/alpha'); + }); + + it('should handle multiple dots in filename', () => { + mockDir.setContent({ + 'file.backup.old.ts': 'export const data = {};', + }); + + const result = resolveEntryPath('file.backup.old.ts', mockDir.path); + + expect(result).toBe('file.backup.old'); + }); + + it('should handle simple directory names', () => { + mockDir.setContent({ + 'dev/index.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev', mockDir.path); + + expect(result).toBe('dev/index'); + }); + + it('should handle nested directory paths', () => { + mockDir.setContent({ + 'dev/alpha/index.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev/alpha', mockDir.path); + + expect(result).toBe('dev/alpha/index'); + }); + + it('should return the file when there is a directory with the same name', () => { + mockDir.setContent({ + 'dev/alpha.ts': '// dev app code', + 'dev/app-config.yaml': '// dev app config', + 'dev/alpha/index.ts': '// dev app code', + 'dev/alpha/app-config.yaml': '// dev app config', + }); + + const result = resolveEntryPath('dev/alpha', mockDir.path); + + expect(result).toBe('dev/alpha'); + }); +}); diff --git a/packages/cli/src/modules/build/commands/package/start/startPackage.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.ts index bac7bee68f..368a28b5df 100644 --- a/packages/cli/src/modules/build/commands/package/start/startPackage.ts +++ b/packages/cli/src/modules/build/commands/package/start/startPackage.ts @@ -17,10 +17,26 @@ import { PackageRole } from '@backstage/cli-node'; import { startBackend, startBackendPlugin } from './startBackend'; import { startFrontend } from './startFrontend'; +import { statSync, readdirSync } from 'fs'; +import { resolve, join, parse } from 'path'; + +export function resolveEntryPath( + entrypoint: string = 'dev', + targetDir: string, +): string { + const { dir: entryDir, name: entryName } = parse(entrypoint); + const entryPath = join(entryDir, entryName); + const parentPath = resolve(targetDir, entryDir); + const isFile = readdirSync(parentPath).some(base => { + const path = resolve(parentPath, base); + return statSync(path).isFile(); + }); + return isFile ? entryPath : join(entryPath, 'index'); +} export async function startPackage(options: { role: PackageRole; - entryDir: string; + entrypoint?: string; targetDir: string; configPaths: string[]; checksEnabled: boolean; @@ -47,7 +63,7 @@ export async function startPackage(options: { case 'frontend-plugin-module': return startFrontend({ ...options, - entry: `${options.entryDir ?? 'dev'}/index`, + entry: resolveEntryPath(options.entrypoint, options.targetDir), }); case 'frontend-dynamic-container' as PackageRole: // experimental return startFrontend({ diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index 552a35a316..88706046ee 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -138,8 +138,8 @@ export const buildPlugin = createCliPlugin({ 'Link an external workspace for module resolution', ) .option( - '--entry-dir ', - 'Path to the directory containing the entry point file', + '--entrypoint ', + 'Entry directory path (uses index file) or entry file path (without extension). Defaults to "dev"', ) .action(lazy(() => import('./commands/package/start'), 'command'));