@stackgenhq/backstage-plugin-stackgen-backend 0.3.6-beta.1 → 0.3.7

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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ This plugin is installed via the `@stackgenhq/backstage-plugin-stackgen-backend`
11
11
  yarn --cwd packages/backend add @stackgenhq/backstage-plugin-stackgen-backend
12
12
  ```
13
13
 
14
- No `.yarnrc.yml` GitHub Packages scope is required for released versions.
14
+ No `.yarnrc.yml` GitHub Packages scope is required if you install from npmjs. The same version is also published to GitHub Packages.
15
15
  Then add the plugin to your backend in `packages/backend/src/index.ts`:
16
16
 
17
17
  ```ts
@@ -39,8 +39,7 @@ async function resolveProjectMemberRoleId(stackgenApis, preferredRoleId, logger)
39
39
  return resolved;
40
40
  }
41
41
  } catch (error) {
42
- const detail = await apiObjects.formatClientErrorMessage(error);
43
- logger.warn(`Failed to list roles for member assignment: ${detail}`);
42
+ logger.warn(`Failed to list roles for member assignment: ${String(error)}`);
44
43
  }
45
44
  throw new Error(
46
45
  "Could not resolve a project member roleId. Set stackGen.projectMemberRoleId or ensure RoleManagementApi.listRoles returns roles."
@@ -140,15 +139,10 @@ const assignProjectMembersAction = (config, logger) => {
140
139
  });
141
140
  } catch (error) {
142
141
  const parsedError = await apiObjects.readClientError(error);
143
- const detail = apiObjects.formatApiFailure(
144
- parsedError.status,
145
- parsedError.body,
146
- parsedError.text,
147
- parsedError.requestId
148
- );
149
- if (!isAlreadyMemberError(parsedError.status, detail) && !isAlreadyMemberError(parsedError.status, parsedError.text)) {
142
+ const text = parsedError.text || String(error);
143
+ if (!isAlreadyMemberError(parsedError.status, text)) {
150
144
  throw new Error(
151
- `Failed to assign user ${user.label || user.id} to project: ${detail}`
145
+ `Failed to assign user ${user.label || user.id} to project (${parsedError.status}): ${text.slice(0, 300)}`
152
146
  );
153
147
  }
154
148
  ctx.logger.info(
@@ -172,15 +166,10 @@ const assignProjectMembersAction = (config, logger) => {
172
166
  });
173
167
  } catch (error) {
174
168
  const parsedError = await apiObjects.readClientError(error);
175
- const detail = apiObjects.formatApiFailure(
176
- parsedError.status,
177
- parsedError.body,
178
- parsedError.text,
179
- parsedError.requestId
180
- );
181
- if (!isAlreadyMemberError(parsedError.status, detail) && !isAlreadyMemberError(parsedError.status, parsedError.text)) {
169
+ const text = parsedError.text || String(error);
170
+ if (!isAlreadyMemberError(parsedError.status, text)) {
182
171
  throw new Error(
183
- `Failed to assign groups to project: ${detail}`
172
+ `Failed to assign groups to project (${parsedError.status}): ${text.slice(0, 300)}`
184
173
  );
185
174
  }
186
175
  ctx.logger.info("Some groups were already project members");
@@ -1 +1 @@
1
- {"version":3,"file":"assign_project_members_action.cjs.js","sources":["../../../src/actions/core/assign_project_members_action.ts"],"sourcesContent":["import { createTemplateAction } from \"@backstage/plugin-scaffolder-node\";\nimport { LoggerService } from \"@backstage/backend-plugin-api\";\nimport { Config } from \"@backstage/config\";\nimport { z } from \"zod\";\nimport {\n formatApiFailure,\n formatClientErrorMessage,\n getStackgenApiObjects,\n readClientError,\n} from \"../../utils/api-objects\";\n\nconst memberSchema = z.object({\n type: z.enum([\"user\", \"group\"]),\n id: z.string().min(1),\n label: z.string().optional(),\n});\n\nexport const assignProjectMembersInputSchema = z.object({\n /** StackGen project / team (org) id. */\n projectId: z.string().min(1, \"projectId is required\"),\n /** Output of the UserGroupSelect scaffolder field. */\n members: z.array(memberSchema).default([]),\n /** Role UUID for assigned users/groups. When omitted, resolves project developer role. */\n roleId: z.string().optional(),\n});\n\nexport const assignProjectMembersOutputSchema = z.object({\n assignedUsers: z.number(),\n assignedGroups: z.number(),\n /** Newline-separated labels for scaffolder output.text tabs. */\n assignedUsersText: z.string(),\n assignedGroupsText: z.string(),\n roleId: z.string().optional(),\n});\n\nasync function resolveProjectMemberRoleId(\n stackgenApis: ReturnType<typeof getStackgenApiObjects>,\n preferredRoleId: string | undefined,\n logger: LoggerService,\n): Promise<string> {\n if (preferredRoleId?.trim()) {\n return preferredRoleId.trim();\n }\n\n try {\n const response = await stackgenApis.roleApiObj.listRoles();\n const roles = response?.roles ?? [];\n const byName = (name: string) =>\n roles.find((role) => role.name?.toLowerCase() === name)?.id?.trim();\n\n // Match appcd-ui project member defaults: developer, then admin, then first role.\n const resolved =\n byName(\"developer\") || byName(\"admin\") || roles[0]?.id?.trim();\n if (resolved) {\n logger.info(`Resolved project member roleId=${resolved}`);\n return resolved;\n }\n } catch (error) {\n const detail = await formatClientErrorMessage(error);\n logger.warn(`Failed to list roles for member assignment: ${detail}`);\n }\n\n throw new Error(\n \"Could not resolve a project member roleId. Set stackGen.projectMemberRoleId or ensure RoleManagementApi.listRoles returns roles.\",\n );\n}\n\nfunction readUserId(row: any): string {\n return String(row?.id || row?.userId || row?.user?.id || \"\").trim();\n}\n\nfunction readGroupId(row: any): string {\n return String(row?.groupId || row?.id || row?.group?.id || \"\").trim();\n}\n\nfunction isAlreadyMemberError(status: number, text: string): boolean {\n const lowered = text.toLowerCase();\n return (\n status === 409 ||\n lowered.includes(\"already member\") ||\n lowered.includes(\"already exists\") ||\n lowered.includes(\"already assigned\")\n );\n}\n\nasync function loadProjectMembership(\n stackgenApis: ReturnType<typeof getStackgenApiObjects>,\n projectId: string,\n): Promise<{ userIds: Set<string>; groupIds: Set<string> }> {\n const orgUsers = await stackgenApis.projectApiObject.getOrganizationUsers({\n orgId: projectId,\n });\n const orgGroups = await stackgenApis.projectApiObject.getOrganizationGroups({\n orgId: projectId,\n });\n const userRows = Array.isArray(orgUsers) ? orgUsers : [];\n let groupRows: any[];\n if (Array.isArray(orgGroups?.groupAssignments)) {\n groupRows = orgGroups.groupAssignments;\n } else if (Array.isArray(orgGroups)) {\n groupRows = orgGroups;\n } else {\n groupRows = [];\n }\n\n return {\n userIds: new Set(userRows.map(readUserId).filter(Boolean)),\n groupIds: new Set(groupRows.map(readGroupId).filter(Boolean)),\n };\n}\n\n/**\n * Assigns users and/or groups from UserGroupSelect to a StackGen project.\n * Uses the same OrgManagement APIs as appcd-ui project membership.\n */\nexport const assignProjectMembersAction = (\n config: Config,\n logger: LoggerService,\n) => {\n return createTemplateAction({\n id: \"stackGen:assignProjectMembers\",\n description:\n \"Assigns StackGen users and/or groups to a project (from UserGroupSelect)\",\n schema: {\n input: () => assignProjectMembersInputSchema,\n output: () => assignProjectMembersOutputSchema,\n },\n async handler(ctx) {\n const stackgenApis = getStackgenApiObjects(config);\n const projectId = ctx.input.projectId.trim();\n const members = ctx.input.members ?? [];\n\n const users = members.filter((member) => member.type === \"user\");\n const groups = members.filter((member) => member.type === \"group\");\n const formatMemberLines = (\n list: Array<{ id: string; label?: string }>,\n emptyLabel: string,\n ) =>\n list.length === 0\n ? emptyLabel\n : list\n .map((member) => member.label?.trim() || member.id)\n .join(\"\\n\");\n\n if (users.length === 0 && groups.length === 0) {\n ctx.logger.info(\"No members selected; skipping assignment\");\n ctx.output(\"assignedUsers\", 0);\n ctx.output(\"assignedGroups\", 0);\n ctx.output(\"assignedUsersText\", \"No users assigned\");\n ctx.output(\"assignedGroupsText\", \"No groups assigned\");\n return;\n }\n\n const roleId = await resolveProjectMemberRoleId(\n stackgenApis,\n ctx.input.roleId?.trim() ||\n config.getOptionalString(\"stackGen.projectMemberRoleId\")?.trim(),\n logger,\n );\n\n // Project create / createAppStack often already added the current user.\n // Skip existing members so an \"already member\" response cannot block the rest.\n let existing = await loadProjectMembership(stackgenApis, projectId);\n ctx.logger.info(\n `Project ${projectId} currently has ${existing.userIds.size} user(s) and ${existing.groupIds.size} group(s)`,\n );\n\n const usersToAdd = users.filter((user) => !existing.userIds.has(user.id));\n const groupsToAdd = groups.filter(\n (group) => !existing.groupIds.has(group.id),\n );\n const skippedUsers = users.filter((user) => existing.userIds.has(user.id));\n const skippedGroups = groups.filter((group) =>\n existing.groupIds.has(group.id),\n );\n\n if (skippedUsers.length > 0) {\n ctx.logger.info(\n `Skipping ${skippedUsers.length} already-present user(s): ${skippedUsers\n .map((user) => user.label || user.id)\n .join(\", \")}`,\n );\n }\n if (skippedGroups.length > 0) {\n ctx.logger.info(\n `Skipping ${skippedGroups.length} already-present group(s): ${skippedGroups\n .map((group) => group.label || group.id)\n .join(\", \")}`,\n );\n }\n\n // Assign users one-by-one so one conflict cannot drop the rest of the batch.\n for (const user of usersToAdd) {\n ctx.logger.info(\n `Assigning user ${user.label || user.id} to project ${projectId} with role ${roleId}`,\n );\n try {\n await stackgenApis.projectApiObject.assignUsersToOrg({\n orgId: projectId,\n assignUsersToOrgRequest: {\n userAssignments: [{ userId: user.id, roleId }],\n } as never,\n });\n } catch (error) {\n const parsedError = await readClientError(error);\n const detail = formatApiFailure(\n parsedError.status,\n parsedError.body,\n parsedError.text,\n parsedError.requestId,\n );\n if (\n !isAlreadyMemberError(parsedError.status, detail) &&\n !isAlreadyMemberError(parsedError.status, parsedError.text)\n ) {\n throw new Error(\n `Failed to assign user ${user.label || user.id} to project: ${detail}`,\n );\n }\n ctx.logger.info(\n `User ${user.label || user.id} was already a project member`,\n );\n }\n }\n\n if (groupsToAdd.length > 0) {\n ctx.logger.info(\n `Assigning ${groupsToAdd.length} group(s) to project ${projectId} with role ${roleId}`,\n );\n try {\n await stackgenApis.projectApiObject.assignGroupsToOrg({\n orgId: projectId,\n assignGroupsToOrgRequest: {\n groupAssignments: groupsToAdd.map((group) => ({\n groupId: group.id,\n roleId,\n })),\n },\n });\n } catch (error) {\n const parsedError = await readClientError(error);\n const detail = formatApiFailure(\n parsedError.status,\n parsedError.body,\n parsedError.text,\n parsedError.requestId,\n );\n if (\n !isAlreadyMemberError(parsedError.status, detail) &&\n !isAlreadyMemberError(parsedError.status, parsedError.text)\n ) {\n throw new Error(\n `Failed to assign groups to project: ${detail}`,\n );\n }\n ctx.logger.info(\"Some groups were already project members\");\n }\n }\n\n existing = await loadProjectMembership(stackgenApis, projectId);\n ctx.logger.info(\n `Project ${projectId} now has ${existing.userIds.size} user(s) and ${existing.groupIds.size} group(s)`,\n );\n\n const missingUsers = users.filter(\n (user) => !existing.userIds.has(user.id),\n );\n if (missingUsers.length > 0) {\n throw new Error(\n `Assigned users but ${missingUsers.length} are still missing from project members: ${missingUsers\n .map((user) => user.label || user.id)\n .join(\", \")}. Check roleId and project id.`,\n );\n }\n\n const missingGroups = groups.filter(\n (group) => !existing.groupIds.has(group.id),\n );\n if (missingGroups.length > 0) {\n throw new Error(\n `Assigned groups but ${missingGroups.length} are still missing from project members: ${missingGroups\n .map((group) => group.label || group.id)\n .join(\", \")}. Check roleId and project id.`,\n );\n }\n\n ctx.output(\"assignedUsers\", users.length);\n ctx.output(\"assignedGroups\", groups.length);\n ctx.output(\n \"assignedUsersText\",\n formatMemberLines(users, \"No users assigned\"),\n );\n ctx.output(\n \"assignedGroupsText\",\n formatMemberLines(groups, \"No groups assigned\"),\n );\n ctx.output(\"roleId\", roleId);\n },\n });\n};\n"],"names":["z","formatClientErrorMessage","createTemplateAction","getStackgenApiObjects","readClientError","formatApiFailure"],"mappings":"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"}
1
+ {"version":3,"file":"assign_project_members_action.cjs.js","sources":["../../../src/actions/core/assign_project_members_action.ts"],"sourcesContent":["import { createTemplateAction } from \"@backstage/plugin-scaffolder-node\";\nimport { LoggerService } from \"@backstage/backend-plugin-api\";\nimport { Config } from \"@backstage/config\";\nimport { z } from \"zod\";\nimport { getStackgenApiObjects, readClientError } from \"../../utils/api-objects\";\n\nconst memberSchema = z.object({\n type: z.enum([\"user\", \"group\"]),\n id: z.string().min(1),\n label: z.string().optional(),\n});\n\nexport const assignProjectMembersInputSchema = z.object({\n /** StackGen project / team (org) id. */\n projectId: z.string().min(1, \"projectId is required\"),\n /** Output of the UserGroupSelect scaffolder field. */\n members: z.array(memberSchema).default([]),\n /** Role UUID for assigned users/groups. When omitted, resolves project developer role. */\n roleId: z.string().optional(),\n});\n\nexport const assignProjectMembersOutputSchema = z.object({\n assignedUsers: z.number(),\n assignedGroups: z.number(),\n /** Newline-separated labels for scaffolder output.text tabs. */\n assignedUsersText: z.string(),\n assignedGroupsText: z.string(),\n roleId: z.string().optional(),\n});\n\nasync function resolveProjectMemberRoleId(\n stackgenApis: ReturnType<typeof getStackgenApiObjects>,\n preferredRoleId: string | undefined,\n logger: LoggerService,\n): Promise<string> {\n if (preferredRoleId?.trim()) {\n return preferredRoleId.trim();\n }\n\n try {\n const response = await stackgenApis.roleApiObj.listRoles();\n const roles = response?.roles ?? [];\n const byName = (name: string) =>\n roles.find((role) => role.name?.toLowerCase() === name)?.id?.trim();\n\n // Match appcd-ui project member defaults: developer, then admin, then first role.\n const resolved =\n byName(\"developer\") || byName(\"admin\") || roles[0]?.id?.trim();\n if (resolved) {\n logger.info(`Resolved project member roleId=${resolved}`);\n return resolved;\n }\n } catch (error) {\n logger.warn(`Failed to list roles for member assignment: ${String(error)}`);\n }\n\n throw new Error(\n \"Could not resolve a project member roleId. Set stackGen.projectMemberRoleId or ensure RoleManagementApi.listRoles returns roles.\",\n );\n}\n\nfunction readUserId(row: any): string {\n return String(row?.id || row?.userId || row?.user?.id || \"\").trim();\n}\n\nfunction readGroupId(row: any): string {\n return String(row?.groupId || row?.id || row?.group?.id || \"\").trim();\n}\n\nfunction isAlreadyMemberError(status: number, text: string): boolean {\n const lowered = text.toLowerCase();\n return (\n status === 409 ||\n lowered.includes(\"already member\") ||\n lowered.includes(\"already exists\") ||\n lowered.includes(\"already assigned\")\n );\n}\n\nasync function loadProjectMembership(\n stackgenApis: ReturnType<typeof getStackgenApiObjects>,\n projectId: string,\n): Promise<{ userIds: Set<string>; groupIds: Set<string> }> {\n const orgUsers = await stackgenApis.projectApiObject.getOrganizationUsers({\n orgId: projectId,\n });\n const orgGroups = await stackgenApis.projectApiObject.getOrganizationGroups({\n orgId: projectId,\n });\n const userRows = Array.isArray(orgUsers) ? orgUsers : [];\n let groupRows: any[];\n if (Array.isArray(orgGroups?.groupAssignments)) {\n groupRows = orgGroups.groupAssignments;\n } else if (Array.isArray(orgGroups)) {\n groupRows = orgGroups;\n } else {\n groupRows = [];\n }\n\n return {\n userIds: new Set(userRows.map(readUserId).filter(Boolean)),\n groupIds: new Set(groupRows.map(readGroupId).filter(Boolean)),\n };\n}\n\n/**\n * Assigns users and/or groups from UserGroupSelect to a StackGen project.\n * Uses the same OrgManagement APIs as appcd-ui project membership.\n */\nexport const assignProjectMembersAction = (\n config: Config,\n logger: LoggerService,\n) => {\n return createTemplateAction({\n id: \"stackGen:assignProjectMembers\",\n description:\n \"Assigns StackGen users and/or groups to a project (from UserGroupSelect)\",\n schema: {\n input: () => assignProjectMembersInputSchema,\n output: () => assignProjectMembersOutputSchema,\n },\n async handler(ctx) {\n const stackgenApis = getStackgenApiObjects(config);\n const projectId = ctx.input.projectId.trim();\n const members = ctx.input.members ?? [];\n\n const users = members.filter((member) => member.type === \"user\");\n const groups = members.filter((member) => member.type === \"group\");\n const formatMemberLines = (\n list: Array<{ id: string; label?: string }>,\n emptyLabel: string,\n ) =>\n list.length === 0\n ? emptyLabel\n : list\n .map((member) => member.label?.trim() || member.id)\n .join(\"\\n\");\n\n if (users.length === 0 && groups.length === 0) {\n ctx.logger.info(\"No members selected; skipping assignment\");\n ctx.output(\"assignedUsers\", 0);\n ctx.output(\"assignedGroups\", 0);\n ctx.output(\"assignedUsersText\", \"No users assigned\");\n ctx.output(\"assignedGroupsText\", \"No groups assigned\");\n return;\n }\n\n const roleId = await resolveProjectMemberRoleId(\n stackgenApis,\n ctx.input.roleId?.trim() ||\n config.getOptionalString(\"stackGen.projectMemberRoleId\")?.trim(),\n logger,\n );\n\n // Project create / createAppStack often already added the current user.\n // Skip existing members so an \"already member\" response cannot block the rest.\n let existing = await loadProjectMembership(stackgenApis, projectId);\n ctx.logger.info(\n `Project ${projectId} currently has ${existing.userIds.size} user(s) and ${existing.groupIds.size} group(s)`,\n );\n\n const usersToAdd = users.filter((user) => !existing.userIds.has(user.id));\n const groupsToAdd = groups.filter(\n (group) => !existing.groupIds.has(group.id),\n );\n const skippedUsers = users.filter((user) => existing.userIds.has(user.id));\n const skippedGroups = groups.filter((group) =>\n existing.groupIds.has(group.id),\n );\n\n if (skippedUsers.length > 0) {\n ctx.logger.info(\n `Skipping ${skippedUsers.length} already-present user(s): ${skippedUsers\n .map((user) => user.label || user.id)\n .join(\", \")}`,\n );\n }\n if (skippedGroups.length > 0) {\n ctx.logger.info(\n `Skipping ${skippedGroups.length} already-present group(s): ${skippedGroups\n .map((group) => group.label || group.id)\n .join(\", \")}`,\n );\n }\n\n // Assign users one-by-one so one conflict cannot drop the rest of the batch.\n for (const user of usersToAdd) {\n ctx.logger.info(\n `Assigning user ${user.label || user.id} to project ${projectId} with role ${roleId}`,\n );\n try {\n await stackgenApis.projectApiObject.assignUsersToOrg({\n orgId: projectId,\n assignUsersToOrgRequest: {\n userAssignments: [{ userId: user.id, roleId }],\n } as never,\n });\n } catch (error) {\n const parsedError = await readClientError(error);\n const text = parsedError.text || String(error);\n if (!isAlreadyMemberError(parsedError.status, text)) {\n throw new Error(\n `Failed to assign user ${user.label || user.id} to project (${parsedError.status}): ${text.slice(0, 300)}`,\n );\n }\n ctx.logger.info(\n `User ${user.label || user.id} was already a project member`,\n );\n }\n }\n\n if (groupsToAdd.length > 0) {\n ctx.logger.info(\n `Assigning ${groupsToAdd.length} group(s) to project ${projectId} with role ${roleId}`,\n );\n try {\n await stackgenApis.projectApiObject.assignGroupsToOrg({\n orgId: projectId,\n assignGroupsToOrgRequest: {\n groupAssignments: groupsToAdd.map((group) => ({\n groupId: group.id,\n roleId,\n })),\n },\n });\n } catch (error) {\n const parsedError = await readClientError(error);\n const text = parsedError.text || String(error);\n if (!isAlreadyMemberError(parsedError.status, text)) {\n throw new Error(\n `Failed to assign groups to project (${parsedError.status}): ${text.slice(0, 300)}`,\n );\n }\n ctx.logger.info(\"Some groups were already project members\");\n }\n }\n\n existing = await loadProjectMembership(stackgenApis, projectId);\n ctx.logger.info(\n `Project ${projectId} now has ${existing.userIds.size} user(s) and ${existing.groupIds.size} group(s)`,\n );\n\n const missingUsers = users.filter(\n (user) => !existing.userIds.has(user.id),\n );\n if (missingUsers.length > 0) {\n throw new Error(\n `Assigned users but ${missingUsers.length} are still missing from project members: ${missingUsers\n .map((user) => user.label || user.id)\n .join(\", \")}. Check roleId and project id.`,\n );\n }\n\n const missingGroups = groups.filter(\n (group) => !existing.groupIds.has(group.id),\n );\n if (missingGroups.length > 0) {\n throw new Error(\n `Assigned groups but ${missingGroups.length} are still missing from project members: ${missingGroups\n .map((group) => group.label || group.id)\n .join(\", \")}. Check roleId and project id.`,\n );\n }\n\n ctx.output(\"assignedUsers\", users.length);\n ctx.output(\"assignedGroups\", groups.length);\n ctx.output(\n \"assignedUsersText\",\n formatMemberLines(users, \"No users assigned\"),\n );\n ctx.output(\n \"assignedGroupsText\",\n formatMemberLines(groups, \"No groups assigned\"),\n );\n ctx.output(\"roleId\", roleId);\n },\n });\n};\n"],"names":["z","createTemplateAction","getStackgenApiObjects","readClientError"],"mappings":"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"}
@@ -218,17 +218,8 @@ function validateExpectedJsonResponse(responseText, endpoint) {
218
218
  );
219
219
  }
220
220
  }
221
- function formatRequestFailure(res) {
222
- return apiObjects.formatApiFailure(res.status, res.parsed, res.text, res.requestId);
223
- }
224
- async function detailFromThrownError(error) {
225
- if (error?.response) {
226
- return apiObjects.formatClientErrorMessage(error);
227
- }
228
- if (error instanceof Error) {
229
- return error.message;
230
- }
231
- return String(error);
221
+ function getErrorMessage(parsedBody, responseText) {
222
+ return parsedBody?.msg || parsedBody?.message || parsedBody?.error || responseText || "Unknown error";
232
223
  }
233
224
  async function cloneTopologyFromTemplateAppstack(params) {
234
225
  const {
@@ -261,7 +252,7 @@ async function cloneTopologyFromTemplateAppstack(params) {
261
252
  );
262
253
  if (!res.ok) {
263
254
  throw new Error(
264
- `Failed to clone topology from template AppStack: ${formatRequestFailure(res)}`
255
+ `Failed to clone topology from template AppStack: ${res.status} ${getErrorMessage(res.parsed, res.text)}`
265
256
  );
266
257
  }
267
258
  validateExpectedJsonResponse(
@@ -303,7 +294,7 @@ async function fetchTemplateAppStackSummary(params) {
303
294
  );
304
295
  if (!res.ok) {
305
296
  throw new Error(
306
- `Failed to list template AppStacks from appcd: ${formatRequestFailure(res)}`
297
+ `Failed to list template AppStacks from appcd: ${res.status} ${getErrorMessage(res.parsed, res.text)}`
307
298
  );
308
299
  }
309
300
  validateExpectedJsonResponse(res.text, "appStacksApiObj.getTemplateAppstacks");
@@ -353,7 +344,7 @@ async function fetchAppStackSnapshotForTemplateClone(params) {
353
344
  );
354
345
  if (!res.ok) {
355
346
  logger.warn(
356
- `[${traceId}] could not load AppStack ${templateUuid} via appcd GET (${formatRequestFailure(res)}); using tenant template summary / defaults for clone`
347
+ `[${traceId}] could not load AppStack ${templateUuid} via appcd GET (${res.status} ${getErrorMessage(res.parsed, res.text)}); using tenant template summary / defaults for clone`
357
348
  );
358
349
  return void 0;
359
350
  }
@@ -380,7 +371,7 @@ async function ensureProjectMembershipForAppstackCreate(params) {
380
371
  );
381
372
  if (!meResponse.ok) {
382
373
  throw new Error(
383
- `Failed to fetch PAT user from auth/me: ${formatRequestFailure(meResponse)}`
374
+ `Failed to fetch PAT user from auth/me: ${meResponse.status} ${getErrorMessage(meResponse.parsed, meResponse.text)}`
384
375
  );
385
376
  }
386
377
  validateExpectedJsonResponse(meResponse.text, "authenticationApiObject.getMe");
@@ -418,7 +409,7 @@ async function ensureProjectMembershipForAppstackCreate(params) {
418
409
  };
419
410
  }
420
411
  throw new Error(
421
- `Failed to assign PAT user to project before appstack creation: ${formatRequestFailure(addUserResponse)}`
412
+ `Failed to assign PAT user to project before appstack creation: ${addUserResponse.status} ${getErrorMessage(addUserResponse.parsed, addUserResponse.text)}`
422
413
  );
423
414
  }
424
415
  validateExpectedJsonResponse(
@@ -479,7 +470,7 @@ async function resolveTeamNameForUrl(stackgenApis, teamId, logger, traceId) {
479
470
  return teamName || void 0;
480
471
  } catch (error) {
481
472
  logger.warn(
482
- `[${traceId}] unable to resolve team name for appStack URL: ${await detailFromThrownError(error)}`
473
+ `[${traceId}] unable to resolve team name for appStack URL: ${String(error)}`
483
474
  );
484
475
  return void 0;
485
476
  }
@@ -618,7 +609,7 @@ const createAppStackAction = (config, logger) => {
618
609
  const blankCreateBody = blankCreateResponse.parsed;
619
610
  const blankCreateText = blankCreateResponse.text;
620
611
  if (!blankCreateResponse.ok) {
621
- const errorMessage = formatRequestFailure(blankCreateResponse);
612
+ const errorMessage = blankCreateBody?.msg || blankCreateBody?.message || blankCreateText || "Unknown error";
622
613
  const isLikelyExistsError = blankCreateResponse.status === 409 || blankCreateResponse.status === 400;
623
614
  if (isLikelyExistsError) {
624
615
  try {
@@ -674,9 +665,8 @@ const createAppStackAction = (config, logger) => {
674
665
  if (errorString.includes("already exists. View it here")) {
675
666
  throw resolveError;
676
667
  }
677
- const resolveDetail = await detailFromThrownError(resolveError);
678
668
  ctx.logger.warn(
679
- `[${traceId}] failed to resolve existing appstack for error message: ${resolveDetail}`
669
+ `[${traceId}] failed to resolve existing appstack for error message: ${resolveError}`
680
670
  );
681
671
  }
682
672
  }
@@ -693,7 +683,7 @@ const createAppStackAction = (config, logger) => {
693
683
  }
694
684
  }
695
685
  throw new Error(
696
- `Failed to create blank appstack: ${errorMessage}.${membershipHint}`
686
+ `Failed to create blank appstack: ${blankCreateResponse.status} ${errorMessage}.${membershipHint}`
697
687
  );
698
688
  } else {
699
689
  validateExpectedJsonResponse(blankCreateText, blankCreateUrl);
@@ -717,8 +707,9 @@ const createAppStackAction = (config, logger) => {
717
707
  const listBody = listResponse.parsed;
718
708
  const listText = listResponse.text;
719
709
  if (!listResponse.ok) {
710
+ const errorMessage = listBody?.msg || listBody?.message || listText || "Unknown error";
720
711
  throw new Error(
721
- `Failed to resolve appstack ID: ${formatRequestFailure(listResponse)}`
712
+ `Failed to resolve appstack ID: ${listResponse.status} ${errorMessage}`
722
713
  );
723
714
  }
724
715
  validateExpectedJsonResponse(listText, listUrl);
@@ -774,10 +765,12 @@ const createAppStackAction = (config, logger) => {
774
765
  }),
775
766
  201
776
767
  );
768
+ const createResourceBody = createResourceResponse.parsed;
777
769
  const createResourceText = createResourceResponse.text;
778
770
  if (!createResourceResponse.ok) {
771
+ const errorMessage = createResourceBody?.msg || createResourceBody?.message || createResourceText || "Unknown error";
779
772
  throw new Error(
780
- `Failed to add resource pack to topology: ${formatRequestFailure(createResourceResponse)}`
773
+ `Failed to add resource pack to topology: ${createResourceResponse.status} ${errorMessage}`
781
774
  );
782
775
  }
783
776
  validateExpectedJsonResponse(createResourceText, createResourceUrl);
@@ -811,10 +804,12 @@ const createAppStackAction = (config, logger) => {
811
804
  }),
812
805
  201
813
806
  );
807
+ const addResourceBody = addResourceResponse.parsed;
814
808
  const addResourceText = addResourceResponse.text;
815
809
  if (!addResourceResponse.ok) {
810
+ const errorMessage = addResourceBody?.msg || addResourceBody?.message || addResourceText || "Unknown error";
816
811
  throw new Error(
817
- `Failed to add resource '${resource.resourceType}' to topology: ${formatRequestFailure(addResourceResponse)}`
812
+ `Failed to add resource '${resource.resourceType}' to topology: ${addResourceResponse.status} ${errorMessage}`
818
813
  );
819
814
  }
820
815
  validateExpectedJsonResponse(addResourceText, addResourceUrl);
@@ -874,7 +869,7 @@ const createAppStackAction = (config, logger) => {
874
869
  const responseText = response.text;
875
870
  ctx.logger.info(`[${traceId}] Response status: ${response.status}`);
876
871
  if (!response.ok) {
877
- const errorMessage = formatRequestFailure(response);
872
+ const errorMessage = responseBody?.msg || responseBody?.message || responseText || "Unknown error";
878
873
  ctx.logger.error(
879
874
  `[${traceId}] Error response: ${errorMessage}. Diagnostics: ${JSON.stringify({
880
875
  teamId: appstack.teamId,
@@ -888,7 +883,7 @@ const createAppStackAction = (config, logger) => {
888
883
  })}`
889
884
  );
890
885
  throw new Error(
891
- `Failed to create appstack: ${errorMessage}`
886
+ `Failed to create appstack: ${response.status} ${errorMessage}`
892
887
  );
893
888
  }
894
889
  validateExpectedJsonResponse(responseText, requestUrl);
@@ -935,7 +930,7 @@ const createAppStackAction = (config, logger) => {
935
930
  )
936
931
  );
937
932
  } catch (error) {
938
- const errorString = await detailFromThrownError(error);
933
+ const errorString = String(error);
939
934
  let errorMessage = `Failed to create appStack: ${errorString}`;
940
935
  if (membershipPreflightWarning && !errorString.includes("already exists. View it here")) {
941
936
  errorMessage += ` (membership preflight warning: ${membershipPreflightWarning})`;