create-svc 0.1.34 → 0.1.35

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.34",
3
+ "version": "0.1.35",
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",
@@ -5,6 +5,7 @@ import { config } from "./config";
5
5
  import { deleteBranch, deleteDatabase, listBranches, resolveNeonConfig } from "./neon";
6
6
  import {
7
7
  assertOwnedResource,
8
+ deleteArtifactImage,
8
9
  deleteProject,
9
10
  deleteProductionDomainMapping,
10
11
  deleteSecret,
@@ -15,6 +16,7 @@ import {
15
16
  describeSecret,
16
17
  formatError,
17
18
  listCloudRunServices,
19
+ listArtifactImages,
18
20
  listSecrets,
19
21
  parseCleanupArgs,
20
22
  requireCommand,
@@ -50,6 +52,7 @@ type DestroyPlan = {
50
52
  hasProductionDomainMapping: boolean;
51
53
  serviceNames: string[];
52
54
  secretNames: string[];
55
+ artifactImages: string[];
53
56
  neon?: {
54
57
  projectId: string;
55
58
  baseBranchId: string;
@@ -91,6 +94,13 @@ export async function cleanup(args = Bun.argv.slice(2)) {
91
94
  }
92
95
  });
93
96
 
97
+ const artifactImages = plan.artifactImages;
98
+ await runStep("Deleting Artifact Registry images", () => {
99
+ for (const image of artifactImages) {
100
+ deleteArtifactImage(image);
101
+ }
102
+ });
103
+
94
104
  const secretNames = plan.secretNames;
95
105
  await runStep("Deleting service secrets", () => {
96
106
  for (const secretName of secretNames) {
@@ -137,12 +147,14 @@ async function buildDestroyPlan(destroyProject: boolean): Promise<DestroyPlan> {
137
147
  hasProductionDomainMapping: false,
138
148
  serviceNames: [],
139
149
  secretNames: [],
150
+ artifactImages: [],
140
151
  };
141
152
 
142
153
  planGitHubRepository(plan);
143
154
  await planLocalDev(plan);
144
155
  planProductionDomainMapping(plan);
145
156
  planCloudRunServices(plan);
157
+ planArtifactImages(plan);
146
158
  planSecrets(plan);
147
159
  await planNeon(plan);
148
160
  await planGrafana(plan);
@@ -236,6 +248,21 @@ function planCloudRunServices(plan: DestroyPlan) {
236
248
  }
237
249
  }
238
250
 
251
+ function planArtifactImages(plan: DestroyPlan) {
252
+ try {
253
+ plan.artifactImages = listArtifactImages();
254
+ if (plan.artifactImages.length === 0) {
255
+ plan.skipped.push({ label: `Artifact Registry images for ${config.serviceName}`, detail: "none matched" });
256
+ return;
257
+ }
258
+ for (const image of plan.artifactImages) {
259
+ plan.resources.push({ label: `Artifact Registry image ${image}`, detail: `${config.project.id}/${config.region}` });
260
+ }
261
+ } catch (error) {
262
+ plan.blockers.push(`Artifact Registry images for ${config.serviceName}: ${formatError(error)}`);
263
+ }
264
+ }
265
+
239
266
  function planSecrets(plan: DestroyPlan) {
240
267
  try {
241
268
  plan.secretNames = listSecrets().filter(matchesSecretResource);
@@ -415,6 +415,32 @@ export function ensureArtifactRepository() {
415
415
  ]);
416
416
  }
417
417
 
418
+ export function artifactImageBase() {
419
+ return `${config.region}-docker.pkg.dev/${config.project.id}/${config.artifactRepository}/${config.serviceName}`;
420
+ }
421
+
422
+ export function listArtifactImages() {
423
+ const result = gcloud(
424
+ ["artifacts", "docker", "images", "list", artifactImageBase(), "--include-tags", "--project", config.project.id, "--format=json"],
425
+ { allowFailure: true }
426
+ );
427
+ if (!result.success || !result.stdout) {
428
+ return [];
429
+ }
430
+
431
+ try {
432
+ return (JSON.parse(result.stdout) as Array<{ package?: string; version?: string }>)
433
+ .map((image) => (image.package && image.version ? `${image.package}@${image.version}` : ""))
434
+ .filter(Boolean);
435
+ } catch {
436
+ throw new Error(`Unable to parse Artifact Registry images for ${artifactImageBase()}`);
437
+ }
438
+ }
439
+
440
+ export function deleteArtifactImage(image: string) {
441
+ gcloud(["artifacts", "docker", "images", "delete", image, "--delete-tags", "--project", config.project.id, "--quiet"], { allowFailure: true });
442
+ }
443
+
418
444
  export function projectNumber() {
419
445
  return gcloud(["projects", "describe", config.project.id, "--format=value(projectNumber)"]).stdout;
420
446
  }
@@ -425,7 +451,7 @@ export function imageTag() {
425
451
  }
426
452
 
427
453
  export function imageUrl(tag = imageTag()) {
428
- return `${config.region}-docker.pkg.dev/${config.project.id}/${config.artifactRepository}/${config.serviceName}:${tag}`;
454
+ return `${artifactImageBase()}:${tag}`;
429
455
  }
430
456
 
431
457
  export function parseDeployArgs(argv: string[]): DeployArgs {