diff --git a/src/components/AppCreateConfigFile/index.js b/src/components/AppCreateConfigFile/index.js index 00cbaa060..219a09b9c 100644 --- a/src/components/AppCreateConfigFile/index.js +++ b/src/components/AppCreateConfigFile/index.js @@ -24,11 +24,20 @@ import cookie from '@/utils/cookie'; import CodeBuildConfig from '../CodeBuildConfig'; import PriceCard from '../../components/PriceCard'; import handleAPIError from '../../utils/error'; +const { mergeCreateRuntimeInfo } = require('../CodeBuildConfig/buildEnvHelpers'); import styles from './setting.less'; const RadioButton = Radio.Button; const RadioGroup = Radio.Group; const { Option } = Select; +const SOURCE_BUILD_CONFIG_KEY = 'source_build_config'; +const readSourceBuildConfig = () => { + if (typeof window === 'undefined' || !window.sessionStorage) { + return null; + } + const config = window.sessionStorage.getItem(SOURCE_BUILD_CONFIG_KEY); + return config ? JSON.parse(config) : null; +}; @connect(null, null, null, { withRef: true }) @Form.create() // 基础信息设置 @@ -1126,7 +1135,10 @@ class RenderDeploy extends PureComponent { }, callback: data => { if (data) { - this.setState({ runtimeInfo: data.bean ? data.bean : {} }); + const runtimeInfo = data.bean ? data.bean : {}; + this.setState({ + runtimeInfo: mergeCreateRuntimeInfo(runtimeInfo, readSourceBuildConfig()) + }); } }, handleError: err => { diff --git a/src/components/AppCreateConfigFile/index.node.test.js b/src/components/AppCreateConfigFile/index.node.test.js new file mode 100644 index 000000000..963a6a1cf --- /dev/null +++ b/src/components/AppCreateConfigFile/index.node.test.js @@ -0,0 +1,19 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const source = fs.readFileSync(path.join(__dirname, 'index.js'), 'utf8'); + +assert.match( + source, + /const \{ mergeCreateRuntimeInfo \} = require\('\.\.\/CodeBuildConfig\/buildEnvHelpers'\);/, + 'AppCreateConfigFile should import mergeCreateRuntimeInfo for create-flow runtime hydration' +); + +assert.match( + source, + /runtimeInfo:\s*mergeCreateRuntimeInfo\(runtimeInfo,\s*readSourceBuildConfig\(\)\)/, + 'AppCreateConfigFile should prefer the latest source-build detection when initializing runtimeInfo' +); + +console.log('app create config file runtime merge test passed'); diff --git a/src/components/CodeBuildConfig/buildEnvHelpers.js b/src/components/CodeBuildConfig/buildEnvHelpers.js index 46c269131..a812aed26 100644 --- a/src/components/CodeBuildConfig/buildEnvHelpers.js +++ b/src/components/CodeBuildConfig/buildEnvHelpers.js @@ -25,6 +25,21 @@ const isStaticNodeFramework = framework => typeof framework === 'string' && (framework === 'other-static' || framework.endsWith('-static')); +const mergeCreateRuntimeInfo = (runtimeInfo = {}, sourceBuildConfig = null) => { + const mergedRuntimeInfo = { ...(runtimeInfo || {}) }; + const buildEnvDict = sourceBuildConfig?.build_env_dict; + + if (buildEnvDict && typeof buildEnvDict === 'object') { + Object.assign(mergedRuntimeInfo, buildEnvDict); + } + + if (sourceBuildConfig?.build_strategy) { + mergedRuntimeInfo.build_strategy = sourceBuildConfig.build_strategy; + } + + return mergedRuntimeInfo; +}; + const mergeRuntimeBuildEnvs = (existingEnvs = {}, fieldsValue = {}) => { const mergedValues = { ...(existingEnvs || {}), ...(fieldsValue || {}) }; @@ -71,5 +86,6 @@ const mergeRuntimeBuildEnvs = (existingEnvs = {}, fieldsValue = {}) => { module.exports = { isBuildEnvTruthy, + mergeCreateRuntimeInfo, mergeRuntimeBuildEnvs }; diff --git a/src/components/CodeBuildConfig/buildEnvHelpers.node.test.js b/src/components/CodeBuildConfig/buildEnvHelpers.node.test.js index 944d660fa..559dc609d 100644 --- a/src/components/CodeBuildConfig/buildEnvHelpers.node.test.js +++ b/src/components/CodeBuildConfig/buildEnvHelpers.node.test.js @@ -1,6 +1,7 @@ const assert = require('assert'); const { isBuildEnvTruthy, + mergeCreateRuntimeInfo, mergeRuntimeBuildEnvs } = require('./buildEnvHelpers'); @@ -16,6 +17,104 @@ assert.strictEqual( 'should treat case-insensitive false strings as disabled' ); +assert.deepStrictEqual( + mergeCreateRuntimeInfo( + { + CNB_FRAMEWORK: 'other-static', + CNB_BUILD_SCRIPT: 'build', + KEEP_ME: 'present' + }, + { + build_strategy: 'cnb', + build_env_dict: { + CNB_FRAMEWORK: 'nestjs', + CNB_BUILD_SCRIPT: 'start', + CNB_OUTPUT_DIR: 'dist' + } + } + ), + { + CNB_FRAMEWORK: 'nestjs', + CNB_BUILD_SCRIPT: 'start', + CNB_OUTPUT_DIR: 'dist', + KEEP_ME: 'present', + build_strategy: 'cnb' + }, + 'should prefer the latest source-build detection over stale backend runtime info during create flow' +); + +assert.deepStrictEqual( + mergeCreateRuntimeInfo( + { + BP_JVM_VERSION: '11', + BUILD_PROCFILE: 'web: java -jar old.jar', + BUILD_MAVEN_SETTING_NAME: 'corp-mirror' + }, + { + build_strategy: 'cnb', + build_env_dict: { + BP_JVM_VERSION: '17', + BUILD_PROCFILE: 'web: java -jar new.jar' + } + } + ), + { + BP_JVM_VERSION: '17', + BUILD_PROCFILE: 'web: java -jar new.jar', + BUILD_MAVEN_SETTING_NAME: 'corp-mirror', + build_strategy: 'cnb' + }, + 'should merge java create-flow envs without dropping unrelated runtime settings' +); + +assert.deepStrictEqual( + mergeCreateRuntimeInfo( + { + BP_CPYTHON_VERSION: '3.10', + PIP_INDEX_URL: 'https://old.example.com/simple', + BUILD_PYTHON_PACKAGE_MANAGER: 'pip' + }, + { + build_strategy: 'cnb', + build_env_dict: { + BP_CPYTHON_VERSION: '3.11', + PIP_INDEX_URL: 'https://new.example.com/simple' + } + } + ), + { + BP_CPYTHON_VERSION: '3.11', + PIP_INDEX_URL: 'https://new.example.com/simple', + BUILD_PYTHON_PACKAGE_MANAGER: 'pip', + build_strategy: 'cnb' + }, + 'should merge python create-flow envs so newer detection can override stale runtime values' +); + +assert.deepStrictEqual( + mergeCreateRuntimeInfo( + { + BP_GO_VERSION: '1.22', + GOPROXY: 'https://goproxy.cn', + BUILD_PROCFILE: '' + }, + { + build_strategy: 'cnb', + build_env_dict: { + BP_GO_VERSION: '1.23', + GOPROXY: 'https://goproxy.io' + } + } + ), + { + BP_GO_VERSION: '1.23', + GOPROXY: 'https://goproxy.io', + BUILD_PROCFILE: '', + build_strategy: 'cnb' + }, + 'should merge golang create-flow envs using the latest detected values' +); + assert.deepStrictEqual( mergeRuntimeBuildEnvs( { diff --git a/src/pages/AppVersion/index.js b/src/pages/AppVersion/index.js index 457d4f130..550fbbc81 100644 --- a/src/pages/AppVersion/index.js +++ b/src/pages/AppVersion/index.js @@ -51,6 +51,10 @@ import AppExporter from '../EnterpriseShared/AppExporter'; import { runCloseCallback } from './closeCallback'; import styles from './index.less'; +const { + getLatestSnapshotVersionSeed +} = require('../Group/components/snapshotVersionHelpers'); + const { Panel } = Collapse; @connect(({ application, user, teamControl, enterprise, loading }) => ({ @@ -1936,13 +1940,14 @@ export default class AppVersion extends PureComponent { const bean = data && data.bean; const recordId = bean && bean.ID; const appModelId = (bean && bean.app_id) || overview.template_id; + const latestSnapshotVersion = getLatestSnapshotVersionSeed(overview); if (!recordId) { notification.error({ message: '打开快照配置页失败' }); return; } dispatch( routerRedux.push( - `/team/${teamName}/region/${regionName}/apps/${appID}/share/${recordId}/one?mode=snapshot${appModelId ? `&preferred_app_id=${appModelId}` : ''}${overview.current_version ? `&latest_snapshot_version=${overview.current_version}` : ''}` + `/team/${teamName}/region/${regionName}/apps/${appID}/share/${recordId}/one?mode=snapshot${appModelId ? `&preferred_app_id=${appModelId}` : ''}${latestSnapshotVersion ? `&latest_snapshot_version=${latestSnapshotVersion}` : ''}` ) ); } diff --git a/src/pages/Create/create-configFile.js b/src/pages/Create/create-configFile.js index dec5e96d4..755a4f650 100644 --- a/src/pages/Create/create-configFile.js +++ b/src/pages/Create/create-configFile.js @@ -21,6 +21,9 @@ const readSourceBuildConfig = () => { const config = window.sessionStorage.getItem(SOURCE_BUILD_CONFIG_KEY); return config ? JSON.parse(config) : null; }; +const saveSourceBuildConfig = (config) => { + window.sessionStorage.setItem(SOURCE_BUILD_CONFIG_KEY, JSON.stringify(config)); +}; @connect( ({ loading, teamControl, user }) => ({ @@ -281,6 +284,16 @@ export default class Index extends PureComponent { }, callback: res => { if (res && res.status_code === 200) { + const sourceBuildConfig = readSourceBuildConfig(); + if (sourceBuildConfig) { + saveSourceBuildConfig({ + ...sourceBuildConfig, + build_env_dict: { + ...(sourceBuildConfig.build_env_dict || {}), + ...(build_env_dict || {}) + } + }); + } this.loadDetail(); } resolve(res); diff --git a/src/pages/Group/components/AppSnapshotSetting.js b/src/pages/Group/components/AppSnapshotSetting.js index 26d6455d0..97e3af6a3 100644 --- a/src/pages/Group/components/AppSnapshotSetting.js +++ b/src/pages/Group/components/AppSnapshotSetting.js @@ -10,12 +10,16 @@ import { import globalUtil from '../../../utils/global'; import AppShareBase from './AppShareBase'; import { - appShareStateSelector, - buildNextSnapshotVersion, - DEFAULT_SNAPSHOT_VERSION + appShareStateSelector } from './appShareHelpers'; import styles from '../publish.less'; +const { + DEFAULT_SNAPSHOT_VERSION, + buildNextSnapshotVersion, + getLatestSnapshotVersionSeed +} = require('./snapshotVersionHelpers'); + const { TextArea } = Input; const verticalFormItemLayout = { @@ -60,7 +64,7 @@ class AppSnapshotSetting extends AppShareBase { .then(res => { const overview = (res && res.bean) || {}; this.updateSnapshotNextVersion( - buildNextSnapshotVersion(overview.current_version) + buildNextSnapshotVersion(getLatestSnapshotVersionSeed(overview)) ); }) .catch(() => { diff --git a/src/pages/Group/components/appShareHelpers.js b/src/pages/Group/components/appShareHelpers.js index 7604ed9c8..9a2f85d1f 100644 --- a/src/pages/Group/components/appShareHelpers.js +++ b/src/pages/Group/components/appShareHelpers.js @@ -1,6 +1,11 @@ import { formatMessage } from '@/utils/intl'; -export const DEFAULT_SNAPSHOT_VERSION = '0.0.1'; +const { + DEFAULT_SNAPSHOT_VERSION, + buildNextSnapshotVersion +} = require('./snapshotVersionHelpers'); + +export { DEFAULT_SNAPSHOT_VERSION, buildNextSnapshotVersion }; export const appShareStateSelector = ({ user, @@ -18,18 +23,6 @@ export const appShareStateSelector = ({ loading }); -export const buildNextSnapshotVersion = latestVersion => { - if (!latestVersion) { - return DEFAULT_SNAPSHOT_VERSION; - } - const parts = String(latestVersion).split('.'); - if (parts.length !== 3 || parts.some(part => !/^\d+$/.test(part))) { - return DEFAULT_SNAPSHOT_VERSION; - } - const [major, minor, patch] = parts.map(item => Number(item)); - return `${major}.${minor}.${patch + 1}`; -}; - export const validateShareVersion = value => { if (value === '' || !value) { return formatMessage({ id: 'placeholder.appShare.versions_notNull' }); diff --git a/src/pages/Group/components/snapshotVersionHelpers.js b/src/pages/Group/components/snapshotVersionHelpers.js new file mode 100644 index 000000000..13f14b62a --- /dev/null +++ b/src/pages/Group/components/snapshotVersionHelpers.js @@ -0,0 +1,31 @@ +const DEFAULT_SNAPSHOT_VERSION = '0.0.1'; + +function buildNextSnapshotVersion(latestVersion) { + if (!latestVersion) { + return DEFAULT_SNAPSHOT_VERSION; + } + const parts = String(latestVersion).split('.'); + if (parts.some(part => !/^\d+$/.test(part))) { + return DEFAULT_SNAPSHOT_VERSION; + } + if (parts.length === 2) { + return `${parts[0]}.${parts[1]}.1`; + } + if (parts.length === 3) { + return `${parts[0]}.${parts[1]}.${Number(parts[2]) + 1}`; + } + return DEFAULT_SNAPSHOT_VERSION; +} + +function getLatestSnapshotVersionSeed(overview) { + if (!overview || typeof overview !== 'object') { + return ''; + } + return overview.latest_snapshot_version || overview.current_version || ''; +} + +module.exports = { + DEFAULT_SNAPSHOT_VERSION, + buildNextSnapshotVersion, + getLatestSnapshotVersionSeed +}; diff --git a/src/pages/Group/components/snapshotVersionHelpers.node.test.js b/src/pages/Group/components/snapshotVersionHelpers.node.test.js new file mode 100644 index 000000000..8d36e04a9 --- /dev/null +++ b/src/pages/Group/components/snapshotVersionHelpers.node.test.js @@ -0,0 +1,43 @@ +const assert = require('assert'); +const { + buildNextSnapshotVersion, + getLatestSnapshotVersionSeed +} = require('./snapshotVersionHelpers'); + +assert.strictEqual( + getLatestSnapshotVersionSeed({ + current_version: '2.0.0', + latest_snapshot_version: '3.0.0' + }), + '3.0.0', + 'should prefer the latest created snapshot version after a rollback changes the current baseline' +); + +assert.strictEqual( + getLatestSnapshotVersionSeed({ + current_version: '2.0.0', + latest_snapshot_version: '' + }), + '2.0.0', + 'should fall back to the current baseline when there is no latest snapshot version' +); + +assert.strictEqual( + getLatestSnapshotVersionSeed({}), + '', + 'should return an empty seed when no version data is available' +); + +assert.strictEqual( + buildNextSnapshotVersion('3.0'), + '3.0.1', + 'should normalize two-part snapshot versions back to three-part patch versions' +); + +assert.strictEqual( + buildNextSnapshotVersion('3.0.0'), + '3.0.1', + 'should keep incrementing three-part snapshot versions by patch' +); + +console.log('snapshot version helper tests passed');