create-svc 0.1.39 → 0.1.40
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 +1 -1
- package/src/scaffold.test.ts +2 -0
- package/src/service-runtime/cloudrun/deploy-args.ts +90 -0
- package/src/service-runtime/cloudrun/deploy.ts +13 -3
- package/src/service-runtime/cloudrun/lib.test.ts +29 -0
- package/src/service-runtime/cloudrun/lib.ts +6 -62
- package/templates/shared/README.md +4 -0
- package/templates/shared/service.jsonc +0 -1
package/package.json
CHANGED
package/src/scaffold.test.ts
CHANGED
|
@@ -70,6 +70,8 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
|
|
|
70
70
|
expect(serviceConfig).toContain('"api_key_secret_name": "dns-api-temporal-api-key"');
|
|
71
71
|
expect(serviceConfig).toContain('"project_mode": "create_new"');
|
|
72
72
|
expect(serviceConfig).toContain('"quota_project_id": "anmho-infra-prod"');
|
|
73
|
+
expect(serviceConfig).toContain('"artifact_repository": "cloud-run"');
|
|
74
|
+
expect(serviceConfig).not.toContain("cloudbuild.googleapis.com");
|
|
73
75
|
expect(serviceConfig).toContain('"jwks_url": "https://auth.anmho.com/api/auth/jwks"');
|
|
74
76
|
expect(serviceConfig).toContain('"git": {');
|
|
75
77
|
expect(serviceConfig).toContain('"repository": "dns-api"');
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type DeployArgs = {
|
|
2
|
+
build: "local" | "cloudbuild";
|
|
3
|
+
ci: boolean;
|
|
4
|
+
destroy: boolean;
|
|
5
|
+
environment: "main" | "preview" | "personal";
|
|
6
|
+
name?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function parseDeployArgs(argv: string[]): DeployArgs {
|
|
10
|
+
const parsed: DeployArgs = {
|
|
11
|
+
build: parseBuildStrategy(process.env.SERVICE_BUILD_STRATEGY || process.env.SERVICE_BUILD),
|
|
12
|
+
ci: false,
|
|
13
|
+
destroy: false,
|
|
14
|
+
environment: "main",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
18
|
+
const token = argv[i];
|
|
19
|
+
if (!token) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const next = argv[i + 1];
|
|
24
|
+
const readValue = () => {
|
|
25
|
+
if (!next || next.startsWith("-")) {
|
|
26
|
+
throw new Error(`Missing value for ${token}`);
|
|
27
|
+
}
|
|
28
|
+
i += 1;
|
|
29
|
+
return next;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (token === "--ci") {
|
|
33
|
+
parsed.ci = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (token === "--destroy") {
|
|
38
|
+
parsed.destroy = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (token === "--build") {
|
|
43
|
+
parsed.build = parseBuildStrategy(readValue());
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (token.startsWith("--build=")) {
|
|
48
|
+
parsed.build = parseBuildStrategy(token.slice("--build=".length));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (token === "--cloud-build") {
|
|
53
|
+
parsed.build = "cloudbuild";
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (token === "--environment") {
|
|
58
|
+
parsed.environment = readValue() as DeployArgs["environment"];
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (token.startsWith("--environment=")) {
|
|
63
|
+
parsed.environment = token.slice("--environment=".length) as DeployArgs["environment"];
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (token === "--name") {
|
|
68
|
+
parsed.name = readValue();
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (token.startsWith("--name=")) {
|
|
73
|
+
parsed.name = token.slice("--name=".length);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseBuildStrategy(value: string | undefined): DeployArgs["build"] {
|
|
82
|
+
if (!value || value === "local") {
|
|
83
|
+
return "local";
|
|
84
|
+
}
|
|
85
|
+
if (value === "cloudbuild" || value === "cloud-build") {
|
|
86
|
+
return "cloudbuild";
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Unknown build strategy: ${value}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
@@ -4,6 +4,7 @@ import { deleteBranch, ensureBranch, ensureDatabase, getConnectionUri, listBranc
|
|
|
4
4
|
import {
|
|
5
5
|
addSecretVersion,
|
|
6
6
|
deleteService,
|
|
7
|
+
dockerStreaming,
|
|
7
8
|
ensureArtifactRepository,
|
|
8
9
|
ensureProductionDomainMapping,
|
|
9
10
|
ensureSecretAccessor,
|
|
@@ -74,9 +75,18 @@ export async function deploy(args = Bun.argv.slice(2), deployOptions: DeployOpti
|
|
|
74
75
|
});
|
|
75
76
|
}
|
|
76
77
|
const image = imageUrl();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
if (options.build === "cloudbuild") {
|
|
79
|
+
await runStep("Building container image in Cloud Build", () =>
|
|
80
|
+
gcloudStreaming(["builds", "submit", "--project", config.project.id, "--region", config.region, "--tag", image])
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
requireCommand("docker");
|
|
84
|
+
await runStep("Authenticating Docker to Artifact Registry", () =>
|
|
85
|
+
gcloud(["auth", "configure-docker", `${config.region}-docker.pkg.dev`, "--quiet"])
|
|
86
|
+
);
|
|
87
|
+
await runStep("Building container image locally", () => dockerStreaming(["build", "-t", image, "."]));
|
|
88
|
+
await runStep("Pushing container image to Artifact Registry", () => dockerStreaming(["push", image]));
|
|
89
|
+
}
|
|
80
90
|
|
|
81
91
|
const renderedManifestPath = await runStep("Rendering Cloud Run manifest", () => writeRenderedManifest(image, target));
|
|
82
92
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { parseDeployArgs } from "./deploy-args";
|
|
3
|
+
|
|
4
|
+
const originalBuild = process.env.SERVICE_BUILD;
|
|
5
|
+
const originalBuildStrategy = process.env.SERVICE_BUILD_STRATEGY;
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
process.env.SERVICE_BUILD = originalBuild;
|
|
9
|
+
process.env.SERVICE_BUILD_STRATEGY = originalBuildStrategy;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("parseDeployArgs defaults to local image builds", () => {
|
|
13
|
+
delete process.env.SERVICE_BUILD;
|
|
14
|
+
delete process.env.SERVICE_BUILD_STRATEGY;
|
|
15
|
+
|
|
16
|
+
expect(parseDeployArgs([]).build).toBe("local");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("parseDeployArgs accepts explicit Cloud Build fallback", () => {
|
|
20
|
+
expect(parseDeployArgs(["--build", "cloudbuild"]).build).toBe("cloudbuild");
|
|
21
|
+
expect(parseDeployArgs(["--build=cloud-build"]).build).toBe("cloudbuild");
|
|
22
|
+
expect(parseDeployArgs(["--cloud-build"]).build).toBe("cloudbuild");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("parseDeployArgs accepts build strategy from env", () => {
|
|
26
|
+
process.env.SERVICE_BUILD_STRATEGY = "cloudbuild";
|
|
27
|
+
|
|
28
|
+
expect(parseDeployArgs([]).build).toBe("cloudbuild");
|
|
29
|
+
});
|
|
@@ -2,6 +2,7 @@ import { intro, log, outro, spinner } from "@clack/prompts";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { config } from "./config";
|
|
4
4
|
import { serviceRoot } from "../runtime";
|
|
5
|
+
import { parseDeployArgs, type DeployArgs } from "./deploy-args";
|
|
5
6
|
|
|
6
7
|
type CommandOptions = {
|
|
7
8
|
allowFailure?: boolean;
|
|
@@ -9,13 +10,6 @@ type CommandOptions = {
|
|
|
9
10
|
env?: Record<string, string | undefined>;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
type DeployArgs = {
|
|
13
|
-
ci: boolean;
|
|
14
|
-
destroy: boolean;
|
|
15
|
-
environment: "main" | "preview" | "personal";
|
|
16
|
-
name?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
13
|
type CleanupArgs = {
|
|
20
14
|
destroyProject: boolean;
|
|
21
15
|
force: boolean;
|
|
@@ -125,6 +119,10 @@ export async function gcloudStreaming(args: string[], options: CommandOptions =
|
|
|
125
119
|
return runStreaming("gcloud", normalized, options);
|
|
126
120
|
}
|
|
127
121
|
|
|
122
|
+
export async function dockerStreaming(args: string[], options: CommandOptions = {}) {
|
|
123
|
+
return runStreaming("docker", args, options);
|
|
124
|
+
}
|
|
125
|
+
|
|
128
126
|
export async function runStreaming(command: string, args: string[], options: CommandOptions = {}): Promise<CommandResult> {
|
|
129
127
|
const child = Bun.spawn([command, ...args], {
|
|
130
128
|
cwd: process.cwd(),
|
|
@@ -454,61 +452,7 @@ export function imageUrl(tag = imageTag()) {
|
|
|
454
452
|
return `${artifactImageBase()}:${tag}`;
|
|
455
453
|
}
|
|
456
454
|
|
|
457
|
-
export
|
|
458
|
-
const parsed: DeployArgs = {
|
|
459
|
-
ci: false,
|
|
460
|
-
destroy: false,
|
|
461
|
-
environment: "main",
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
465
|
-
const token = argv[i];
|
|
466
|
-
if (!token) {
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const next = argv[i + 1];
|
|
471
|
-
const readValue = () => {
|
|
472
|
-
if (!next || next.startsWith("-")) {
|
|
473
|
-
throw new Error(`Missing value for ${token}`);
|
|
474
|
-
}
|
|
475
|
-
i += 1;
|
|
476
|
-
return next;
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
if (token === "--ci") {
|
|
480
|
-
parsed.ci = true;
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (token === "--destroy") {
|
|
485
|
-
parsed.destroy = true;
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (token === "--environment") {
|
|
490
|
-
parsed.environment = readValue() as DeployArgs["environment"];
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (token.startsWith("--environment=")) {
|
|
495
|
-
parsed.environment = token.slice("--environment=".length) as DeployArgs["environment"];
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (token === "--name") {
|
|
500
|
-
parsed.name = readValue();
|
|
501
|
-
continue;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (token.startsWith("--name=")) {
|
|
505
|
-
parsed.name = token.slice("--name=".length);
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return parsed;
|
|
511
|
-
}
|
|
455
|
+
export { parseDeployArgs };
|
|
512
456
|
|
|
513
457
|
export function parseCleanupArgs(argv: string[]): CleanupArgs {
|
|
514
458
|
const parsed: CleanupArgs = {
|
|
@@ -66,10 +66,14 @@ Create, deploy, and destroy use:
|
|
|
66
66
|
|
|
67
67
|
- known-good CLIs first, especially `gcloud`
|
|
68
68
|
- `gcloud`
|
|
69
|
+
- Docker for local image builds and Artifact Registry pushes
|
|
69
70
|
- `NEON_API_KEY`, or a working Vault login via `VAULT_ADDR` plus `VAULT_TOKEN`, `VAULT_TOKEN_FILE`, or `~/.vault-token`
|
|
70
71
|
- the repo-aware `service` CLI from this package
|
|
71
72
|
|
|
72
73
|
Local provisioning intentionally prefers known-good CLIs over SDKs for Google Cloud operations.
|
|
74
|
+
Cloud Run deploys build and push the container image locally by default. Use
|
|
75
|
+
`service deploy --build cloudbuild` only when you explicitly want Google Cloud
|
|
76
|
+
Build to build the image remotely.
|
|
73
77
|
|
|
74
78
|
Authenticate `gcloud` on the machine before running provisioning commands:
|
|
75
79
|
|