create-svc 0.1.36 → 0.1.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-svc",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Local microservice bootstrap CLI for Cloud Run and Workers services with Neon-backed data.",
5
5
  "module": "index.ts",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -1131,7 +1131,7 @@ async function promptForExistingProject(projects: GcpProject[], options: { allow
1131
1131
  : []),
1132
1132
  ...projects.map((project) => ({
1133
1133
  value: project.projectId,
1134
- label: project.name,
1134
+ label: project.name ?? project.projectId,
1135
1135
  hint: project.projectId,
1136
1136
  })),
1137
1137
  ],
@@ -1151,7 +1151,7 @@ async function promptForExistingProject(projects: GcpProject[], options: { allow
1151
1151
  return {
1152
1152
  mode: "use_existing" as const,
1153
1153
  projectId: project.projectId,
1154
- projectName: project.name,
1154
+ projectName: project.name ?? project.projectId,
1155
1155
  };
1156
1156
  }
1157
1157
 
package/src/gcp.test.ts CHANGED
@@ -23,6 +23,42 @@ test("listAccessibleProjects filters deleted projects and sorts by name", async
23
23
  ]);
24
24
  });
25
25
 
26
+ test("listAccessibleProjects tolerates projects without a display name", async () => {
27
+ const api: GcpApi = {
28
+ async listProjects() {
29
+ return [{ projectId: "b" }, { projectId: "a", name: "alpha" }];
30
+ },
31
+ async listBillingAccounts() {
32
+ return [];
33
+ },
34
+ async createProject() {},
35
+ async attachBillingAccount() {},
36
+ };
37
+
38
+ await expect(listAccessibleProjects(api)).resolves.toEqual([{ projectId: "a", name: "alpha" }, { projectId: "b" }]);
39
+ });
40
+
41
+ test("listOpenBillingAccounts tolerates accounts without a display name", async () => {
42
+ const api: GcpApi = {
43
+ async listProjects() {
44
+ return [];
45
+ },
46
+ async listBillingAccounts() {
47
+ return [
48
+ { name: "billingAccounts/2", displayName: "", open: true },
49
+ { name: "billingAccounts/1", displayName: "A", open: true },
50
+ ];
51
+ },
52
+ async createProject() {},
53
+ async attachBillingAccount() {},
54
+ };
55
+
56
+ await expect(listOpenBillingAccounts(api)).resolves.toEqual([
57
+ { name: "billingAccounts/1", displayName: "A", open: true },
58
+ { name: "billingAccounts/2", displayName: "", open: true },
59
+ ]);
60
+ });
61
+
26
62
  test("listOpenBillingAccounts keeps only open accounts", async () => {
27
63
  const api: GcpApi = {
28
64
  async listProjects() {
package/src/gcp.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type GcpProject = {
2
2
  projectId: string;
3
- name: string;
3
+ name?: string;
4
4
  lifecycleState?: string;
5
5
  };
6
6
 
@@ -47,13 +47,13 @@ export function createGcpApi(): GcpApi {
47
47
  export async function listAccessibleProjects(api = createGcpApi()): Promise<GcpProject[]> {
48
48
  return (await api.listProjects())
49
49
  .filter((project) => project.projectId && project.lifecycleState !== "DELETE_REQUESTED")
50
- .sort((left, right) => left.name.localeCompare(right.name));
50
+ .sort((left, right) => projectSortName(left).localeCompare(projectSortName(right)));
51
51
  }
52
52
 
53
53
  export async function listOpenBillingAccounts(api = createGcpApi()): Promise<BillingAccount[]> {
54
54
  return (await api.listBillingAccounts())
55
55
  .filter((account) => account.name && account.open)
56
- .sort((left, right) => left.displayName.localeCompare(right.displayName));
56
+ .sort((left, right) => accountSortName(left).localeCompare(accountSortName(right)));
57
57
  }
58
58
 
59
59
  export async function createProject(projectId: string, name: string, api = createGcpApi()) {
@@ -79,6 +79,14 @@ function runGcloud(args: string[]) {
79
79
  };
80
80
  }
81
81
 
82
+ function projectSortName(project: GcpProject) {
83
+ return project.name || project.projectId;
84
+ }
85
+
86
+ function accountSortName(account: BillingAccount) {
87
+ return account.displayName || account.name;
88
+ }
89
+
82
90
  function parseJson<T>(value: string, label: string): T {
83
91
  try {
84
92
  return JSON.parse(value) as T;
package/src/naming.ts CHANGED
@@ -108,7 +108,7 @@ export function buildGcpProjectOptions(
108
108
  serviceName: string,
109
109
  projectId: string,
110
110
  projectName: string,
111
- projects: Array<{ projectId: string; name: string }>
111
+ projects: Array<{ projectId: string; name?: string }>
112
112
  ) {
113
113
  const servicesProject = projects.find((project) => project.projectId === SERVICES_PROJECT_DEFAULT);
114
114
  const remainingProjects = projects.filter((project) => project.projectId !== SERVICES_PROJECT_DEFAULT);
@@ -120,10 +120,10 @@ export function buildGcpProjectOptions(
120
120
  projectName: servicesProject?.name ?? SERVICES_PROJECT_NAME_DEFAULT,
121
121
  },
122
122
  ...remainingProjects.map((project) => ({
123
- label: `Use existing project: ${project.name} (${project.projectId})`,
123
+ label: `Use existing project: ${project.name ?? project.projectId} (${project.projectId})`,
124
124
  mode: "use_existing" as const,
125
125
  projectId: project.projectId,
126
- projectName: project.name,
126
+ projectName: project.name ?? project.projectId,
127
127
  })),
128
128
  {
129
129
  label: buildCreateProjectLabel(serviceName, projectId),