@prisma/cli 3.0.0-alpha.9 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,41 +42,52 @@ async function readAuthState(env) {
42
42
  authenticated: false,
43
43
  provider: null,
44
44
  user: null,
45
- workspace: null
45
+ workspace: null,
46
+ credential: null
46
47
  };
48
+ const client = await requireComputeAuth(env);
49
+ const currentPrincipal = await readCurrentPrincipalAuthState(client);
50
+ if (currentPrincipal) return currentPrincipal;
47
51
  const claims = decodeJwtPayload(tokens.accessToken);
48
52
  return buildAuthState({
49
53
  workspaceIdFromCredential: tokens.workspaceId,
50
54
  claims,
51
- env
55
+ env,
56
+ client
52
57
  });
53
58
  }
54
59
  async function readServiceTokenAuthState(token, env) {
60
+ const client = await requireComputeAuth(env);
61
+ const currentPrincipal = await readCurrentPrincipalAuthState(client);
62
+ if (currentPrincipal) return currentPrincipal;
55
63
  const claims = decodeJwtPayload(token);
56
64
  const workspaceId = workspaceIdFromClaims(claims);
57
65
  if (!workspaceId) return {
58
66
  authenticated: false,
59
67
  provider: null,
60
68
  user: null,
61
- workspace: null
69
+ workspace: null,
70
+ credential: null
62
71
  };
63
72
  return buildAuthState({
64
73
  workspaceIdFromCredential: workspaceId,
65
74
  claims,
66
- env
75
+ env,
76
+ client
67
77
  });
68
78
  }
69
- async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
79
+ async function buildAuthState({ workspaceIdFromCredential, claims, env, client }) {
70
80
  let workspaceId = workspaceIdFromCredential;
71
81
  let workspaceName = workspaceIdFromCredential;
72
- const client = await requireComputeAuth(env);
82
+ client ??= await requireComputeAuth(env);
73
83
  if (client) try {
74
84
  const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
75
85
  if (response?.status === 401) return {
76
86
  authenticated: false,
77
87
  provider: null,
78
88
  user: null,
79
- workspace: null
89
+ workspace: null,
90
+ credential: null
80
91
  };
81
92
  if (data?.data?.id) {
82
93
  workspaceId = data.data.id;
@@ -92,9 +103,39 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
92
103
  workspace: {
93
104
  id: workspaceId,
94
105
  name: workspaceName
95
- }
106
+ },
107
+ credential: null
96
108
  };
97
109
  }
110
+ async function readCurrentPrincipalAuthState(client) {
111
+ if (!client) return null;
112
+ try {
113
+ const { data, response } = await client.GET("/v1/me");
114
+ if (response?.status === 401) return {
115
+ authenticated: false,
116
+ provider: null,
117
+ user: null,
118
+ workspace: null,
119
+ credential: null
120
+ };
121
+ const principal = data?.data;
122
+ if (!principal) return null;
123
+ if (!principal.credential) return null;
124
+ return {
125
+ authenticated: true,
126
+ provider: null,
127
+ user: principal.user ? {
128
+ id: principal.user.id,
129
+ email: principal.user.email,
130
+ name: principal.user.name
131
+ } : null,
132
+ workspace: principal.workspace,
133
+ credential: principal.credential
134
+ };
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
98
139
  async function performLogout(env) {
99
140
  await new FileTokenStorage(env).clearTokens();
100
141
  }
@@ -0,0 +1,56 @@
1
+ import { usageError } from "../../shell/errors.js";
2
+ import { selectPrompt, textPrompt } from "../../shell/prompt.js";
3
+ import { inferTargetName, sortProjects } from "./resolution.js";
4
+ import { toProjectSummary, validateProjectSetupNameText } from "./setup.js";
5
+ //#region src/lib/project/interactive-setup.ts
6
+ async function promptForProjectSetupChoice(options) {
7
+ const sortedProjects = sortProjects(options.projects);
8
+ const projectNames = sortedProjects.map((project) => project.name);
9
+ const duplicateNames = new Set(projectNames.filter((name, index) => projectNames.indexOf(name) !== index));
10
+ const choice = await selectPrompt({
11
+ input: options.context.runtime.stdin,
12
+ output: options.context.runtime.stderr,
13
+ message: "Which Project should this directory use?",
14
+ choices: [
15
+ ...sortedProjects.map((project) => ({
16
+ label: duplicateNames.has(project.name) ? `${project.name} (${project.id})` : project.name,
17
+ value: {
18
+ kind: "project",
19
+ project
20
+ }
21
+ })),
22
+ {
23
+ label: "Create a new Project",
24
+ value: { kind: "create" }
25
+ },
26
+ {
27
+ label: "Cancel",
28
+ value: { kind: "cancel" }
29
+ }
30
+ ]
31
+ });
32
+ if (choice.kind === "cancel") throw usageError("Project setup canceled", options.cancel.why, options.cancel.fix, options.cancel.nextSteps, "project");
33
+ if (choice.kind === "project") return {
34
+ project: toProjectSummary(choice.project),
35
+ action: "linked",
36
+ targetName: choice.project.name,
37
+ targetNameSource: "prompt"
38
+ };
39
+ const suggestedName = await inferTargetName(options.context.runtime.cwd);
40
+ const rawName = await textPrompt({
41
+ input: options.context.runtime.stdin,
42
+ output: options.context.runtime.stderr,
43
+ message: "Project name",
44
+ placeholder: suggestedName.name,
45
+ validate: (value) => validateProjectSetupNameText(value, suggestedName.name)
46
+ });
47
+ const projectName = rawName.trim() || suggestedName.name;
48
+ return {
49
+ project: toProjectSummary(await options.createProject(projectName)),
50
+ action: "created",
51
+ targetName: projectName,
52
+ targetNameSource: rawName.trim() ? "prompt" : suggestedName.source
53
+ };
54
+ }
55
+ //#endregion
56
+ export { promptForProjectSetupChoice };
@@ -1,73 +1,33 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
- import { canPrompt } from "../../shell/runtime.js";
2
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
3
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
3
4
  import { readFile } from "node:fs/promises";
4
5
  import path from "node:path";
5
6
  //#region src/lib/project/resolution.ts
6
7
  async function resolveProjectTarget(options) {
7
8
  const projects = await options.listProjects();
8
- const inferredName = await inferTargetName(options.context.runtime.cwd);
9
- if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
10
- targetName: options.explicitProject,
11
- targetNameSource: "explicit"
9
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
+ if (target) return target;
11
+ throw await projectSetupRequiredError({
12
+ cwd: options.context.runtime.cwd,
13
+ projects,
14
+ commandName: options.commandName
12
15
  });
13
- const platformMapping = await resolveDurablePlatformMapping();
14
- if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
15
- let staleRemembered = false;
16
- if (!options.allowCreate) {
17
- const rememberedResult = await resolveRememberedProject(options, projects);
18
- if (rememberedResult.target) return rememberedResult.target;
19
- staleRemembered = rememberedResult.stale;
20
- }
21
- const packageName = inferredName.source === "package-name" ? inferredName.name : null;
22
- if (packageName) {
23
- const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
24
- if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
25
- targetName: packageName,
26
- targetNameSource: "package-name"
27
- });
28
- if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
29
- }
30
- if (options.allowCreate && options.createProject) {
31
- if (inferredName.name) {
32
- const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
33
- if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
34
- targetName: inferredName.name,
35
- targetNameSource: inferredName.source
36
- });
37
- if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
38
- return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
39
- targetName: inferredName.name,
40
- targetNameSource: inferredName.source
41
- });
42
- }
43
- }
44
- if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
45
- message: "Select a project",
46
- choices: sortProjects(projects).map((project) => ({
47
- label: `${project.name} (${project.id})`,
48
- value: project
49
- }))
50
- }), "prompt");
51
- if (staleRemembered && projects.length > 1) throw localStateStaleError();
52
- throw projectUnresolvedError();
53
- }
54
- async function resolveRememberedProject(options, projects) {
55
- const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
56
- if (!remembered) return {
57
- target: null,
58
- stale: false
59
- };
60
- const matched = projects.find((project) => project.id === remembered.id);
61
- if (!matched) return {
62
- target: null,
63
- stale: true
64
- };
16
+ }
17
+ async function inspectProjectBinding(options) {
18
+ const projects = await options.listProjects();
19
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
20
+ if (target) return target;
65
21
  return {
66
- target: await rememberIfRequested(options, matched, "remembered-local", {
67
- targetName: remembered.name,
68
- targetNameSource: "remembered-local"
69
- }),
70
- stale: false
22
+ workspace: options.workspace,
23
+ project: null,
24
+ localBinding: { status: "not-linked" },
25
+ resolution: { projectSource: "unbound" },
26
+ ...await buildProjectSetupSuggestion({
27
+ cwd: options.context.runtime.cwd,
28
+ projects,
29
+ commandName: options.commandName ?? "project show"
30
+ })
71
31
  };
72
32
  }
73
33
  function projectNotFoundError(projectRef, workspace) {
@@ -99,27 +59,77 @@ function projectAmbiguousError(projectRef, matches) {
99
59
  nextSteps
100
60
  });
101
61
  }
102
- function projectUnresolvedError() {
62
+ function localStateStaleError() {
103
63
  return new CliError({
104
- code: "PROJECT_UNRESOLVED",
64
+ code: "LOCAL_STATE_STALE",
105
65
  domain: "project",
106
- summary: "No project is resolved for this directory",
107
- why: "No project could be resolved from explicit input, platform mappings, remembered local context, or package metadata.",
108
- fix: "Pass --project <id-or-name> on the command that needs a project, or add a package.json name that matches an accessible project.",
66
+ summary: "Local project binding is stale",
67
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
68
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
69
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
109
70
  exitCode: 1,
110
- nextSteps: ["prisma-cli project list", "prisma-cli project show --project <id-or-name>"]
71
+ nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
111
72
  });
112
73
  }
113
- function localStateStaleError() {
74
+ async function buildProjectSetupSuggestion(options) {
75
+ const suggestedName = await inferTargetName(options.cwd);
76
+ const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
77
+ return {
78
+ suggestedProjectName: suggestedName.name,
79
+ suggestedProjectNameSource: suggestedName.source,
80
+ candidates,
81
+ recoveryCommands: buildProjectRecoveryCommands(options.commandName)
82
+ };
83
+ }
84
+ async function projectSetupRequiredError(options) {
85
+ const suggestion = await buildProjectSetupSuggestion(options);
114
86
  return new CliError({
115
- code: "LOCAL_STATE_STALE",
87
+ code: "PROJECT_SETUP_REQUIRED",
116
88
  domain: "project",
117
- summary: "Remembered project context is stale",
118
- why: "The remembered project is no longer available in the selected workspace, and automatic resolution would be ambiguous.",
119
- fix: "Pass --project <id-or-name> to choose the project explicitly.",
89
+ summary: "Choose a Project before running this command",
90
+ why: `This directory is not linked to a Prisma Project, and ${options.commandName ? `prisma-cli ${options.commandName}` : "this command"} will not choose one from package or directory names.`,
91
+ fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
92
+ meta: { ...suggestion },
120
93
  exitCode: 1,
121
- nextSteps: ["prisma-cli project list"]
94
+ nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
95
+ nextActions: buildProjectSetupNextActions({
96
+ commandName: options.commandName,
97
+ suggestedProjectName: suggestion.suggestedProjectName
98
+ })
99
+ });
100
+ }
101
+ function buildProjectSetupNextActions(options = {}) {
102
+ const recoveryCommands = buildProjectRecoveryCommands(options.commandName);
103
+ const linkCommand = recoveryCommands[0] ?? "prisma-cli project link <id-or-name>";
104
+ const retryCommand = recoveryCommands[1];
105
+ const actions = [{
106
+ kind: "user-choice",
107
+ journey: "project-setup",
108
+ label: "Ask the user whether to link an existing Project or create a new one",
109
+ commands: ["prisma-cli project list", ...recoveryCommands],
110
+ reason: options.reason ?? "This directory is not linked to a Prisma Project. Package and directory names are suggestions only, not a safe Project selection."
111
+ }, {
112
+ kind: "run-command",
113
+ journey: "project-setup",
114
+ label: "Link the chosen Project",
115
+ command: linkCommand,
116
+ reason: "Linking writes the durable local Project binding for this directory."
117
+ }];
118
+ const createCommand = options.createCommand ?? (options.suggestedProjectName ? `prisma-cli project create ${formatCommandArgument(options.suggestedProjectName)}` : void 0);
119
+ if (createCommand) actions.push({
120
+ kind: "run-command",
121
+ journey: "project-setup",
122
+ label: "Create and link a new Project",
123
+ command: createCommand,
124
+ reason: "Use this when the user wants a new Prisma Project instead of an existing one."
122
125
  });
126
+ if (options.commandName) actions.push({
127
+ kind: "run-command",
128
+ journey: "recover",
129
+ label: "Retry with an explicit Project",
130
+ command: retryCommand ?? `prisma-cli ${options.commandName} --project <id-or-name>`
131
+ });
132
+ return actions;
123
133
  }
124
134
  async function readPackageName(cwd) {
125
135
  try {
@@ -157,33 +167,46 @@ function resolveExplicitProject(projectRef, projects, workspace) {
157
167
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
158
168
  throw projectNotFoundError(projectRef, workspace);
159
169
  }
160
- function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
161
- if (options.prompt && canPrompt(options.context)) return options.prompt.select({
162
- message: "Select a project",
163
- choices: sortProjects(matches).map((project) => ({
164
- label: `${project.name} (${project.id})`,
165
- value: project
166
- }))
167
- }).then((selected) => rememberIfRequested(options, selected, "prompt", {
168
- targetName: projectRef,
169
- targetNameSource
170
- }));
171
- throw projectAmbiguousError(projectRef, matches);
172
- }
173
- function projectMatchesPackageName(project, packageName) {
174
- return project.id === packageName || project.name === packageName || project.slug === packageName;
170
+ function projectMatchesSuggestedName(project, suggestedName) {
171
+ return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
175
172
  }
176
173
  async function resolveDurablePlatformMapping() {
177
174
  return null;
178
175
  }
179
- async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
180
- if (options.remember) await options.context.stateStore.setRememberedProject({
181
- id: project.id,
182
- name: project.name,
183
- workspaceId: options.workspace.id
176
+ async function resolveBoundProjectTarget(options, projects, settings) {
177
+ if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
178
+ targetName: options.explicitProject,
179
+ targetNameSource: "explicit"
184
180
  });
181
+ if (settings.allowEnvProjectId && options.envProjectId) {
182
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
183
+ if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
184
+ return resolvedTarget(options.workspace, project, "env", {
185
+ targetName: options.envProjectId,
186
+ targetNameSource: "env"
187
+ });
188
+ }
189
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
190
+ if (localPin.kind === "invalid") throw localStateStaleError();
191
+ if (localPin.kind === "present") {
192
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
193
+ const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
194
+ if (!project) throw localStateStaleError();
195
+ return resolvedTarget(options.workspace, project, "local-pin", {
196
+ targetName: project.name,
197
+ targetNameSource: "local-pin"
198
+ });
199
+ }
200
+ const platformMapping = await resolveDurablePlatformMapping();
201
+ if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
202
+ targetName: platformMapping.name,
203
+ targetNameSource: "platform-mapping"
204
+ });
205
+ return null;
206
+ }
207
+ function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
185
208
  return {
186
- workspace: options.workspace,
209
+ workspace,
187
210
  project: toProjectSummary(project),
188
211
  resolution: {
189
212
  projectSource,
@@ -191,6 +214,11 @@ async function rememberIfRequested(options, project, projectSource, resolutionDe
191
214
  }
192
215
  };
193
216
  }
217
+ function buildProjectRecoveryCommands(commandName) {
218
+ const commands = ["prisma-cli project link <id-or-name>"];
219
+ if (commandName) commands.push(`prisma-cli ${commandName} --project <id-or-name>`);
220
+ return commands;
221
+ }
194
222
  function toProjectSummary(project) {
195
223
  return {
196
224
  id: project.id,
@@ -198,4 +226,4 @@ function toProjectSummary(project) {
198
226
  };
199
227
  }
200
228
  //#endregion
201
- export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
229
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -0,0 +1,88 @@
1
+ import { CliError, usageError } from "../../shell/errors.js";
2
+ import "../../shell/command-arguments.js";
3
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
4
+ import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
5
+ //#region src/lib/project/setup.ts
6
+ function isValidProjectSetupName(projectName) {
7
+ return projectName.trim().length > 0;
8
+ }
9
+ function validateProjectSetupNameText(value, fallback) {
10
+ if ((value?.trim() || fallback).trim().length > 0) return;
11
+ return "Enter a Project name.";
12
+ }
13
+ function resolveProjectForSetup(projectRef, projects, workspace) {
14
+ const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
15
+ if (matches.length === 1) return matches[0];
16
+ if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
17
+ throw projectNotFoundError(projectRef, workspace);
18
+ }
19
+ async function bindProjectToDirectory(context, workspace, project, action) {
20
+ await writeLocalResolutionPin(context.runtime.cwd, {
21
+ workspaceId: workspace.id,
22
+ projectId: project.id
23
+ });
24
+ await ensureLocalResolutionPinGitignore(context.runtime.cwd);
25
+ return {
26
+ workspace,
27
+ project,
28
+ directory: formatSetupDirectory(context.runtime.cwd),
29
+ localPin: {
30
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
31
+ written: true
32
+ },
33
+ action
34
+ };
35
+ }
36
+ function toProjectSummary(project) {
37
+ return {
38
+ id: project.id,
39
+ name: project.name
40
+ };
41
+ }
42
+ function projectSetupNameRequiredError(command) {
43
+ return usageError("Project create requires a name", "The project name must be a non-empty value.", "Pass a Project name explicitly.", [`prisma-cli ${command} my-app`], "project");
44
+ }
45
+ function projectCreateFailedError(error, projectName, workspace, options) {
46
+ const status = extractHttpStatus(error);
47
+ if (status === 401 || status === 403) return new CliError({
48
+ code: "PROJECT_CREATE_FAILED",
49
+ domain: "project",
50
+ summary: `Could not create Project "${projectName}"`,
51
+ why: `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).`,
52
+ fix: options.permissionFix,
53
+ debug: formatDebugDetails(error),
54
+ exitCode: 1,
55
+ nextSteps: options.nextSteps
56
+ });
57
+ return new CliError({
58
+ code: "PROJECT_CREATE_FAILED",
59
+ domain: "project",
60
+ summary: `Could not create Project "${projectName}"`,
61
+ why: error instanceof Error ? error.message : String(error),
62
+ fix: options.fallbackFix,
63
+ debug: formatDebugDetails(error),
64
+ exitCode: 1,
65
+ nextSteps: options.nextSteps
66
+ });
67
+ }
68
+ function formatSetupDirectory(cwd) {
69
+ const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
70
+ return basename ? `./${basename}` : ".";
71
+ }
72
+ function extractHttpStatus(error) {
73
+ if (!error || typeof error !== "object") return null;
74
+ const candidate = error;
75
+ if (typeof candidate.statusCode === "number") return candidate.statusCode;
76
+ if (typeof candidate.status === "number") return candidate.status;
77
+ if (typeof candidate.message === "string") {
78
+ const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
79
+ if (match) return Number.parseInt(match[1], 10);
80
+ }
81
+ return null;
82
+ }
83
+ function formatDebugDetails(error) {
84
+ if (error instanceof Error) return error.stack ?? error.message;
85
+ return typeof error === "string" ? error : null;
86
+ }
87
+ //#endregion
88
+ export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
@@ -1,7 +1,8 @@
1
1
  import { renderList, renderShow, serializeList } from "../output/patterns.js";
2
2
  //#region src/presenters/app-env.ts
3
3
  function scopeLabel(scope) {
4
- return scope.role;
4
+ if (scope.kind === "role") return scope.role ?? "unknown";
5
+ return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
5
6
  }
6
7
  function renderEnvAdd(context, descriptor, result) {
7
8
  return renderShow({
@@ -79,7 +80,7 @@ function renderEnvList(context, descriptor, result) {
79
80
  },
80
81
  items: result.variables.map((variable) => ({
81
82
  noun: "variable",
82
- label: variable.key,
83
+ label: `${variable.key} (${variable.source})`,
83
84
  id: variable.id,
84
85
  status: variable.isManagedBySystem ? "default" : null
85
86
  })),
@@ -94,7 +95,7 @@ function serializeEnvList(result) {
94
95
  context: { scope: scopeLabel(result.scope) },
95
96
  items: result.variables.map((variable) => ({
96
97
  noun: "variable",
97
- label: variable.key,
98
+ label: `${variable.key} (${variable.source})`,
98
99
  id: variable.id,
99
100
  status: variable.isManagedBySystem ? "default" : null
100
101
  }))