-
Notifications
You must be signed in to change notification settings - Fork 6
test(e2e): modernize plugin e2e harness and dist publish flow #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY | ||
| version = 1 | ||
| name = "nx-forge" | ||
|
|
||
| [setup] | ||
| script = "npm i" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { existsSync, readFileSync } from 'node:fs'; | ||
| import { join } from 'node:path'; | ||
| import { generateForgeApp } from './utils/generate-forge-app'; | ||
| import { runNxCommandAsync } from './utils/async-commands'; | ||
| import { cleanupTestProject, createTestProject } from './utils/test-project'; | ||
|
|
||
| describe('Forge application generator', () => { | ||
| let projectDirectory: string; | ||
|
|
||
| beforeAll(() => { | ||
| projectDirectory = createTestProject(); | ||
| }); | ||
|
|
||
| afterAll(async () => { | ||
| try { | ||
| if (projectDirectory) { | ||
| await runNxCommandAsync('reset', { cwd: projectDirectory }); | ||
| } | ||
| } finally { | ||
| cleanupTestProject(projectDirectory); | ||
| } | ||
| }); | ||
|
|
||
| it('should generate a Forge app', async () => { | ||
| const appName = await generateForgeApp({ | ||
| cwd: projectDirectory, | ||
| directory: 'apps', | ||
| options: '--bundler=webpack', | ||
| }); | ||
| expect( | ||
| existsSync(join(projectDirectory, 'apps', appName, 'manifest.yml')) | ||
| ).toBe(true); | ||
| expect( | ||
| existsSync(join(projectDirectory, 'apps', appName, 'webpack.config.js')) | ||
| ).toBe(true); | ||
| expect( | ||
| existsSync(join(projectDirectory, 'apps', appName, 'src', 'index.ts')) | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| describe('--directory', () => { | ||
| it('should generate a Forge app in the specified directory', async () => { | ||
| const subdir = 'subdir'; | ||
| const appName = await generateForgeApp({ | ||
| cwd: projectDirectory, | ||
| directory: subdir, | ||
| options: `--bundler=webpack`, | ||
| }); | ||
|
|
||
| expect( | ||
| existsSync(join(projectDirectory, subdir, appName, 'manifest.yml')) | ||
| ).toBe(true); | ||
| expect( | ||
| existsSync(join(projectDirectory, subdir, appName, 'webpack.config.js')) | ||
| ).toBe(true); | ||
| expect( | ||
| existsSync(join(projectDirectory, subdir, appName, 'src', 'index.ts')) | ||
| ).toBe(true); | ||
| }); | ||
| }); | ||
|
|
||
| describe('--tags', () => { | ||
| it('should generate a Forge app with tags added to the project', async () => { | ||
| const appName = await generateForgeApp({ | ||
| cwd: projectDirectory, | ||
| directory: 'apps', | ||
| options: `--tags e2etag,e2ePackage`, | ||
| }); | ||
| const project = JSON.parse( | ||
| readFileSync( | ||
| join(projectDirectory, 'apps', appName, 'project.json'), | ||
| 'utf8' | ||
| ) | ||
| ); | ||
| expect(project.tags).toEqual(['e2etag', 'e2ePackage']); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||
| import { readFileSync } from 'node:fs'; | ||||||||||||||||||||||
| import { join } from 'node:path'; | ||||||||||||||||||||||
| import { GraphQLClient } from 'graphql-request'; | ||||||||||||||||||||||
| import { generateForgeApp } from './utils/generate-forge-app'; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| Credentials, | ||||||||||||||||||||||
| ForgeInstallationContext, | ||||||||||||||||||||||
| getCredentials, | ||||||||||||||||||||||
| getDeveloperSpaceId, | ||||||||||||||||||||||
| getForgeInstallationContext, | ||||||||||||||||||||||
| } from './utils/config'; | ||||||||||||||||||||||
| import { createClient, deleteApp } from './utils/atlassian-graphql-client'; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| runCommandAsync, | ||||||||||||||||||||||
| runForgeCommandAsync, | ||||||||||||||||||||||
| runNxCommandAsync, | ||||||||||||||||||||||
| } from './utils/async-commands'; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| cleanupTestProject, | ||||||||||||||||||||||
| createTestProject, | ||||||||||||||||||||||
| } from './utils/test-project'; | ||||||||||||||||||||||
| import stripAnsi = require('strip-ansi'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| describe('Forge lifecycle', () => { | ||||||||||||||||||||||
| // initialize before all tests | ||||||||||||||||||||||
| let projectDirectory: string; | ||||||||||||||||||||||
| let developerCredentials: Credentials; | ||||||||||||||||||||||
| let apiClient: GraphQLClient; | ||||||||||||||||||||||
| let installationContext: ForgeInstallationContext; | ||||||||||||||||||||||
| let developerSpaceId: string; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| beforeAll(async () => { | ||||||||||||||||||||||
| projectDirectory = createTestProject(); | ||||||||||||||||||||||
| developerCredentials = getCredentials(); | ||||||||||||||||||||||
| apiClient = createClient(developerCredentials); | ||||||||||||||||||||||
| installationContext = getForgeInstallationContext(); | ||||||||||||||||||||||
| developerSpaceId = getDeveloperSpaceId(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Initialize the Forge CLI, otherwise commands may fail due to expected interactive input | ||||||||||||||||||||||
| await runCommandAsync(`npx forge settings set usage-analytics false`, { | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| afterAll(async () => { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| if (projectDirectory) { | ||||||||||||||||||||||
| await runNxCommandAsync('reset', { cwd: projectDirectory }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||
| cleanupTestProject(projectDirectory); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| it('should generate, build, package, register, deploy and install a Forge app', async () => { | ||||||||||||||||||||||
| const appName = await generateForgeApp({ | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| directory: 'apps', | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Build | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const nxBuildResult = await runNxCommandAsync(`run ${appName}:build`, { | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| expect(nxBuildResult.stderr).toEqual(''); | ||||||||||||||||||||||
| expect(stripAnsi(nxBuildResult.stdout)).toContain( | ||||||||||||||||||||||
| 'Successfully ran target build for project' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Package | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const nxPackageResult = await runNxCommandAsync(`run ${appName}:package`, { | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| expect(nxPackageResult.stderr).toEqual(''); | ||||||||||||||||||||||
| expect(stripAnsi(nxPackageResult.stdout)).toEqual( | ||||||||||||||||||||||
| expect.stringContaining('Successfully ran target package for project') | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Register | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const unregisteredOutputManifestContent = readFileSync( | ||||||||||||||||||||||
| join(projectDirectory, 'dist', 'apps', appName, 'manifest.yml'), | ||||||||||||||||||||||
| 'utf8' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| expect(unregisteredOutputManifestContent).toContain( | ||||||||||||||||||||||
| 'ari:cloud:ecosystem::app/to-be-generated' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const nxRegisterResult = await runNxCommandAsync( | ||||||||||||||||||||||
| `run ${appName}:register --accept-terms --developer-space-id ${developerSpaceId}`, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| expect(nxRegisterResult.stderr).toEqual(''); | ||||||||||||||||||||||
| expect(stripAnsi(nxRegisterResult.stdout)).toContain( | ||||||||||||||||||||||
| 'Forge app registered' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ari:cloud:ecosystem::app/<uuid> | ||||||||||||||||||||||
| const registeredAppIdRegex = | ||||||||||||||||||||||
| /ari:cloud:ecosystem::app\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const registeredOutputManifestContent = readFileSync( | ||||||||||||||||||||||
| join(projectDirectory, 'dist', 'apps', appName, 'manifest.yml'), | ||||||||||||||||||||||
| 'utf8' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| const [registeredAppId] = | ||||||||||||||||||||||
| registeredOutputManifestContent.match(registeredAppIdRegex) ?? []; | ||||||||||||||||||||||
| expect(registeredAppId).not.toBeNull(); | ||||||||||||||||||||||
| expect(registeredAppId).toBeDefined(); | ||||||||||||||||||||||
| expect(registeredAppId).not.toEqual(''); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const projectManifestContent = readFileSync( | ||||||||||||||||||||||
| join(projectDirectory, 'apps', appName, 'manifest.yml'), | ||||||||||||||||||||||
| 'utf8' | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| expect(projectManifestContent).toContain(registeredAppId); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // Deploy | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Run with `--no-verfiy` because the generated blank app template causes linting errors | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in comment: "verfiy" should be "verify". ✏️ Fix typo- // Run with `--no-verfiy` because the generated blank app template causes linting errors
+ // Run with `--no-verify` because the generated blank app template causes linting errors📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| const nxDeployResult = await runNxCommandAsync( | ||||||||||||||||||||||
| `run ${appName}:deploy --no-verify`, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| cwd: projectDirectory, | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| expect(nxDeployResult.stderr).toEqual(''); | ||||||||||||||||||||||
| expect(stripAnsi(nxDeployResult.stdout)).toContain('Forge app deployed'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Install using Forge CLI | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const installResult = await runForgeCommandAsync( | ||||||||||||||||||||||
| `install --product=${installationContext.product} --site=${installationContext.siteUrl} --environment ${installationContext.environment} --non-interactive`, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| cwd: join(projectDirectory, 'dist', 'apps', appName), | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| expect(installResult.stderr).toEqual(''); | ||||||||||||||||||||||
| expect(stripAnsi(installResult.stdout)).toMatch(/Install.*complete/); | ||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||
| if (registeredAppId) { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| await runForgeCommandAsync( | ||||||||||||||||||||||
| `uninstall --product=${installationContext.product} --site=${installationContext.siteUrl} --environment ${installationContext.environment} --non-interactive`, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| cwd: join(projectDirectory, 'dist', 'apps', appName), | ||||||||||||||||||||||
| silenceError: true, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||
| `Failed to uninstall Forge app ${registeredAppId}`, | ||||||||||||||||||||||
| error | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const result = await deleteApp(registeredAppId)(apiClient); | ||||||||||||||||||||||
| if (!result.success) { | ||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||
| `Failed to delete registered app ${registeredAppId}: ${result.errors}` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+169
to
+173
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error array won't format properly in the warning message.
✏️ Fix error formatting if (!result.success) {
console.warn(
- `Failed to delete registered app ${registeredAppId}: ${result.errors}`
+ `Failed to delete registered app ${registeredAppId}: ${result.errors?.map(e => e.message).join(', ')}`
);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||
| `Failed to delete registered app ${registeredAppId}`, | ||||||||||||||||||||||
| error | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: toolsplus/nx-forge
Length of output: 44
🏁 Script executed:
Repository: toolsplus/nx-forge
Length of output: 1837
🏁 Script executed:
Repository: toolsplus/nx-forge
Length of output: 533
🏁 Script executed:
Repository: toolsplus/nx-forge
Length of output: 10690
Remove the autogenerated comment—this file is manually maintained.
The comment on line 1 claims the file is autogenerated, but no generation script or tool was found in the codebase. Since this environment is being manually created and maintained in the PR, the misleading comment should be removed to avoid confusing future maintainers.
Additionally, this environment is defined but not referenced in
.codex/config.toml, so it won't be activated. Either activate it in the main config or document why this environment definition is needed if it's meant to be optional.Consider using
npm ciinstead ofnpm iin the setup script for reproducible, deterministic builds.🤖 Prompt for AI Agents