mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-17 17:39:10 +08:00
feat(plugin-environment-variables): expose $env on the flow-engine context in v1 too (#9728)
Extract the v2 plugin's `defineProperty('$env')` registration into a
framework-agnostic `registerEnvProperty(context, apiClient, t)` helper, and call
it from the v1 client entry as well (via the allowed v1 -> v2 import). This puts
`$env` on the v1 flow-engine context — the same mechanism v2 already uses — so v2
UI rendered in the v1 runtime (which mounts detached from the v1 React tree and
can't reach the React-context `addGlobalVar('$env')`) can resolve `$env` from
`flowEngine.context.getPropertyMetaTree()`. The list is fetched once and cached by
the flow-engine context. The legacy v1 `addGlobalVar` + provider path is kept
untouched.
This commit is contained in:
@@ -9,28 +9,7 @@
|
||||
|
||||
import type { Application } from '@nocobase/client-v2';
|
||||
import { Plugin } from '@nocobase/client-v2';
|
||||
import type { PropertyMeta } from '@nocobase/flow-engine';
|
||||
|
||||
type EnvironmentVariable = {
|
||||
name: string;
|
||||
value?: string;
|
||||
type?: 'default' | 'secret';
|
||||
};
|
||||
|
||||
const ENV_VARS_LIST_URL = 'environmentVariables?paginate=false';
|
||||
|
||||
async function fetchEnvironmentVariables(apiClient: Application['apiClient']): Promise<EnvironmentVariable[]> {
|
||||
try {
|
||||
const response = await apiClient.request({
|
||||
url: ENV_VARS_LIST_URL,
|
||||
skipNotify: true,
|
||||
} as any);
|
||||
const list = (response as any)?.data?.data;
|
||||
return Array.isArray(list) ? list : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
import { registerEnvProperty } from './registerEnvProperty';
|
||||
|
||||
export class PluginEnvironmentVariablesClientV2 extends Plugin<Record<string, never>, Application> {
|
||||
async load() {
|
||||
@@ -52,28 +31,9 @@ export class PluginEnvironmentVariablesClientV2 extends Plugin<Record<string, ne
|
||||
|
||||
// Expose `$env` to all v2 plugins via FlowContext, replacing v1's
|
||||
// `addGlobalVar('$env', ...)` + Provider chain. Lazy-loaded — the API is
|
||||
// only hit on first read, then cached by FlowContext.
|
||||
this.flowEngine.context.defineProperty('$env', {
|
||||
get: async () => {
|
||||
const list = await fetchEnvironmentVariables(this.app.apiClient);
|
||||
return Object.fromEntries(list.map((item) => [item.name, item.value]));
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
title: this.t('Variables and secrets') as unknown as string,
|
||||
properties: async (): Promise<Record<string, PropertyMeta>> => {
|
||||
const list = await fetchEnvironmentVariables(this.app.apiClient);
|
||||
const out: Record<string, PropertyMeta> = {};
|
||||
for (const item of list) {
|
||||
out[item.name] = {
|
||||
type: item.type === 'secret' ? 'string' : item.type || 'string',
|
||||
title: item.name,
|
||||
};
|
||||
}
|
||||
return out;
|
||||
},
|
||||
},
|
||||
});
|
||||
// only hit on first read, then cached by FlowContext. Shared with the v1
|
||||
// runtime via `registerEnvProperty` (see `./registerEnvProperty`).
|
||||
registerEnvProperty(this.flowEngine.context, this.app.apiClient, (key) => this.t(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared registration of the `$env` property on a flow-engine context — the
|
||||
* single source of truth used by BOTH client runtimes:
|
||||
* - v2 (`client-v2/plugin.tsx`) registers it on its own flow-engine context;
|
||||
* - v1 (`client/index.tsx`) imports this same helper via the allowed v1 → v2
|
||||
* direction and registers it on v1's flow-engine context, so workflow's v2
|
||||
* config drawer (which renders detached from the React tree and therefore
|
||||
* can't reach v1's React-context-based `addGlobalVar('$env')`) can still read
|
||||
* `$env` from `flowEngine.context.getPropertyMetaTree()`.
|
||||
*
|
||||
* Framework-agnostic: dependencies (`apiClient`, `t`) are injected as parameters,
|
||||
* so it carries no React hooks and no `@nocobase/client` import — usable from
|
||||
* either runtime. The list is fetched lazily (first read only) and cached by the
|
||||
* flow-engine context, so the picker never re-requests on every expand.
|
||||
*/
|
||||
|
||||
import type { FlowContext, PropertyMeta } from '@nocobase/flow-engine';
|
||||
|
||||
type EnvironmentVariable = {
|
||||
name: string;
|
||||
value?: string;
|
||||
type?: 'default' | 'secret';
|
||||
};
|
||||
|
||||
/** Minimal structural slice of the API client — only the one call this needs.
|
||||
* Avoids importing the concrete `APIClient` type across the package boundary. */
|
||||
type EnvApiClient = {
|
||||
request(options: { url: string; skipNotify?: boolean }): Promise<unknown>;
|
||||
};
|
||||
|
||||
const ENV_VARS_LIST_URL = 'environmentVariables?paginate=false';
|
||||
const ENV_ROOT = '$env';
|
||||
|
||||
export async function fetchEnvironmentVariables(apiClient: EnvApiClient): Promise<EnvironmentVariable[]> {
|
||||
try {
|
||||
const response = await apiClient.request({ url: ENV_VARS_LIST_URL, skipNotify: true });
|
||||
const list = (response as { data?: { data?: unknown } })?.data?.data;
|
||||
return Array.isArray(list) ? (list as EnvironmentVariable[]) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** A translator for the scope label. Accepts the i18next `t` from either runtime
|
||||
* (its return type isn't a bare `string` — it's a `TFunctionDetailedResult` /
|
||||
* union), so the result is coerced to a string by the helper. */
|
||||
type Translate = (key: string) => unknown;
|
||||
|
||||
/**
|
||||
* Define `$env` on the given flow-engine context. `t` translates the scope label
|
||||
* ("Variables and secrets"); `apiClient` fetches the variable list lazily.
|
||||
*/
|
||||
export function registerEnvProperty(context: FlowContext, apiClient: EnvApiClient, t: Translate): void {
|
||||
context.defineProperty(ENV_ROOT, {
|
||||
get: async () => {
|
||||
const list = await fetchEnvironmentVariables(apiClient);
|
||||
return Object.fromEntries(list.map((item) => [item.name, item.value]));
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
title: String(t('Variables and secrets')),
|
||||
properties: async (): Promise<Record<string, PropertyMeta>> => {
|
||||
const list = await fetchEnvironmentVariables(apiClient);
|
||||
const out: Record<string, PropertyMeta> = {};
|
||||
for (const item of list) {
|
||||
out[item.name] = {
|
||||
type: item.type === 'secret' ? 'string' : item.type || 'string',
|
||||
title: item.name,
|
||||
};
|
||||
}
|
||||
return out;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import { Plugin } from '@nocobase/client';
|
||||
import { EnvironmentVariablesAndSecretsProvider } from './EnvironmentVariablesAndSecretsProvider';
|
||||
import EnvironmentPage from './components/EnvironmentPage';
|
||||
import { useGetEnvironmentVariables, useGetEnvironmentVariablesCtx } from './utils';
|
||||
// Shared `$env` registration, defined once in client-v2 and imported back here
|
||||
// via the allowed v1 → v2 direction (see the helper's doc comment).
|
||||
import { registerEnvProperty } from '../client-v2/registerEnvProperty';
|
||||
|
||||
export class PluginEnvironmentVariablesClient extends Plugin {
|
||||
async load() {
|
||||
@@ -19,8 +22,15 @@ export class PluginEnvironmentVariablesClient extends Plugin {
|
||||
icon: 'TableOutlined',
|
||||
Component: EnvironmentPage,
|
||||
});
|
||||
// Legacy v1 path: `$env` as a React-context global var, consumed by the v1
|
||||
// Formily `Variable.Input` scope (`useGlobalVariable('$env')`). Kept as-is.
|
||||
this.app.addGlobalVar('$env', useGetEnvironmentVariables, useGetEnvironmentVariablesCtx);
|
||||
this.app.use(EnvironmentVariablesAndSecretsProvider);
|
||||
// Also expose `$env` on the flow-engine context (the v2 mechanism), so v2 UI
|
||||
// rendered in the v1 runtime — e.g. workflow's config drawer, which mounts
|
||||
// detached from the React tree and can't read the React-context global var —
|
||||
// can still resolve `$env` via `flowEngine.context.getPropertyMetaTree()`.
|
||||
registerEnvProperty(this.app.flowEngine.context, this.app.apiClient, (key) => this.t(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user