create-svc 0.1.85 → 0.1.86
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 +3 -1
- package/src/service-runtime/authctl-command.ts +12 -0
- package/src/service-runtime/authctl.test.ts +16 -0
- package/src/service-runtime/authctl.ts +21 -3
- package/templates/shared/README.md +4 -4
- package/templates/shared/docker-compose.yml +16 -0
- package/templates/shared/scripts/dev.ts +36 -0
- package/templates/shared/scripts/local-docker.ts +1 -1
package/package.json
CHANGED
package/src/scaffold.test.ts
CHANGED
|
@@ -113,6 +113,8 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
|
|
|
113
113
|
|
|
114
114
|
const dockerCompose = await Bun.file(join(generatedRoot, "docker-compose.yml")).text();
|
|
115
115
|
expect(dockerCompose).toContain('image: postgres:16-alpine');
|
|
116
|
+
expect(dockerCompose).toContain("image: temporalio/auto-setup:");
|
|
117
|
+
expect(dockerCompose).toContain("127.0.0.1:7233:7233");
|
|
116
118
|
expect(dockerCompose).toContain(`127.0.0.1:${localPort}:5432`);
|
|
117
119
|
|
|
118
120
|
const envExample = await Bun.file(join(generatedRoot, ".env.example")).text();
|
|
@@ -337,7 +339,7 @@ test("scaffolds a backend package cleanly into a nested monorepo-style directory
|
|
|
337
339
|
expect(readme).toContain("`microservice` profile");
|
|
338
340
|
expect(readme).toContain("api.dns-api.anmho.com");
|
|
339
341
|
expect(readme).toContain("open Docker Desktop if needed");
|
|
340
|
-
expect(readme).toContain("local Postgres
|
|
342
|
+
expect(readme).toContain("local Postgres and Temporal services in `docker-compose.yml`");
|
|
341
343
|
expect(readme).toContain("gcloud auth login");
|
|
342
344
|
expect(readme).toContain("known-good CLIs");
|
|
343
345
|
expect(readme).toContain("service create");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type AuthctlCommand = {
|
|
2
|
+
path: string;
|
|
3
|
+
runWithBun: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function authctlSpawnArgs(command: AuthctlCommand, args: string[]) {
|
|
7
|
+
return command.runWithBun ? [bunExecutable(), command.path, ...args] : [command.path, ...args];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function bunExecutable() {
|
|
11
|
+
return Bun.which("bun") ?? process.execPath;
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { authctlSpawnArgs } from "./authctl-command";
|
|
3
|
+
|
|
4
|
+
test("authctlSpawnArgs runs repo-local authctl through bun", () => {
|
|
5
|
+
const args = authctlSpawnArgs({ path: "./node_modules/.bin/authctl", runWithBun: true }, ["doctor", "--json"]);
|
|
6
|
+
|
|
7
|
+
expect(args[0]).toEndWith("bun");
|
|
8
|
+
expect(args.slice(1)).toEqual(["./node_modules/.bin/authctl", "doctor", "--json"]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("authctlSpawnArgs runs global authctl directly", () => {
|
|
12
|
+
expect(authctlSpawnArgs({ path: "/usr/local/bin/authctl", runWithBun: false }, ["version"])).toEqual([
|
|
13
|
+
"/usr/local/bin/authctl",
|
|
14
|
+
"version",
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { authctlSpawnArgs, type AuthctlCommand } from "./authctl-command";
|
|
2
3
|
import { serviceConfig } from "./runtime";
|
|
3
4
|
|
|
4
5
|
type CommandResult = {
|
|
@@ -9,6 +10,7 @@ type CommandResult = {
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const decoder = new TextDecoder();
|
|
13
|
+
const localAuthctlPath = "./node_modules/.bin/authctl";
|
|
12
14
|
|
|
13
15
|
export type AuthDoctorResult = {
|
|
14
16
|
hasAuthctl: boolean;
|
|
@@ -418,12 +420,12 @@ function resolveResourceServerCommand(): ResourceServerCommand | undefined {
|
|
|
418
420
|
}
|
|
419
421
|
|
|
420
422
|
function authctl(args: string[], options: { allowFailure?: boolean; quiet?: boolean } = {}): CommandResult {
|
|
421
|
-
const command =
|
|
423
|
+
const command = authctlCommand();
|
|
422
424
|
if (!command) {
|
|
423
425
|
throw new Error("authctl is not installed; run bun install in this generated service or link @anmho/authctl");
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
const result = Bun.spawnSync(
|
|
428
|
+
const result = Bun.spawnSync(authctlSpawnArgs(command, args), {
|
|
427
429
|
cwd: process.cwd(),
|
|
428
430
|
env: authctlEnvironment(),
|
|
429
431
|
stdin: "inherit",
|
|
@@ -464,8 +466,24 @@ function formatAuthctlFailure(args: string[], output: CommandResult) {
|
|
|
464
466
|
return `authctl ${args.join(" ")} failed with exit code ${output.exitCode}\n${detail}`;
|
|
465
467
|
}
|
|
466
468
|
|
|
469
|
+
function authctlCommand(): AuthctlCommand | undefined {
|
|
470
|
+
if (existsSync(localAuthctlPath)) {
|
|
471
|
+
return {
|
|
472
|
+
path: localAuthctlPath,
|
|
473
|
+
runWithBun: true,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const global = Bun.which("authctl");
|
|
477
|
+
return global
|
|
478
|
+
? {
|
|
479
|
+
path: global,
|
|
480
|
+
runWithBun: false,
|
|
481
|
+
}
|
|
482
|
+
: undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
467
485
|
function authctlPath() {
|
|
468
|
-
return
|
|
486
|
+
return authctlCommand()?.path;
|
|
469
487
|
}
|
|
470
488
|
|
|
471
489
|
function authctlEnvironment() {
|
|
@@ -43,7 +43,7 @@ console to create and deploy.
|
|
|
43
43
|
|
|
44
44
|
## Local development
|
|
45
45
|
|
|
46
|
-
The scaffold writes a ready-to-use `.env.local` and includes
|
|
46
|
+
The scaffold writes a ready-to-use `.env.local` and includes local Postgres and Temporal services in `docker-compose.yml`.
|
|
47
47
|
|
|
48
48
|
First local run:
|
|
49
49
|
|
|
@@ -55,8 +55,8 @@ First local run:
|
|
|
55
55
|
Local runtime uses:
|
|
56
56
|
|
|
57
57
|
- `DATABASE_URL` from `.env.local`, pointed at Docker Compose Postgres
|
|
58
|
-
- `{{COMMAND_MIGRATE}}` and `{{COMMAND_DEV}}`, which open Docker Desktop if needed, wait for Docker readiness, and start Docker Compose Postgres
|
|
59
|
-
- `TEMPORAL_ENABLED=true` by default with `localhost:7233` and `default`;
|
|
58
|
+
- `{{COMMAND_MIGRATE}}` and `{{COMMAND_DEV}}`, which open Docker Desktop if needed, wait for Docker readiness, and start Docker Compose Postgres and Temporal
|
|
59
|
+
- `TEMPORAL_ENABLED=true` by default with `localhost:7233` and `default`; `{{COMMAND_DEV}}` waits for the local Temporal container before starting the API and worker
|
|
60
60
|
|
|
61
61
|
No cloud credentials are required for local HTTP development after Docker and Postgres are running.
|
|
62
62
|
|
|
@@ -78,7 +78,7 @@ creates a trigger, the API service starts `waitlistFollowUpWorkflow` /
|
|
|
78
78
|
`WaitlistFollowUpWorkflow` asynchronously on the service task queue. The API
|
|
79
79
|
request only waits for the trigger record; workflow completion happens through
|
|
80
80
|
Temporal and is polled by the worker service.
|
|
81
|
-
Local `{{COMMAND_DEV}}` starts the API process and the worker process together.
|
|
81
|
+
Local `{{COMMAND_DEV}}` starts the API process and the worker process together after Docker Compose starts the local Temporal server.
|
|
82
82
|
|
|
83
83
|
Production and preview deploys render `TEMPORAL_ENABLED=true` into the Cloud Run
|
|
84
84
|
manifest unless you override it. For Temporal Cloud, replace the local defaults
|
|
@@ -15,5 +15,21 @@ services:
|
|
|
15
15
|
timeout: 5s
|
|
16
16
|
retries: 10
|
|
17
17
|
|
|
18
|
+
temporal:
|
|
19
|
+
image: temporalio/auto-setup:1.28.1
|
|
20
|
+
depends_on:
|
|
21
|
+
postgres:
|
|
22
|
+
condition: service_healthy
|
|
23
|
+
environment:
|
|
24
|
+
DB: postgres12
|
|
25
|
+
DB_PORT: 5432
|
|
26
|
+
POSTGRES_USER: {{LOCAL_DATABASE_USER}}
|
|
27
|
+
POSTGRES_PWD: {{LOCAL_DATABASE_PASSWORD}}
|
|
28
|
+
POSTGRES_SEEDS: postgres
|
|
29
|
+
DBNAME: temporal
|
|
30
|
+
VISIBILITY_DBNAME: temporal_visibility
|
|
31
|
+
ports:
|
|
32
|
+
- "127.0.0.1:7233:7233"
|
|
33
|
+
|
|
18
34
|
volumes:
|
|
19
35
|
postgres-data:
|
|
@@ -16,6 +16,9 @@ const env = {
|
|
|
16
16
|
if (env.DATABASE_URL && !env.CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE) {
|
|
17
17
|
env.CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE = env.DATABASE_URL;
|
|
18
18
|
}
|
|
19
|
+
if (temporalEnabled(env)) {
|
|
20
|
+
await waitForTemporal(env.TEMPORAL_ADDRESS || "localhost:7233");
|
|
21
|
+
}
|
|
19
22
|
|
|
20
23
|
const api = Bun.spawn(apiCommand, {
|
|
21
24
|
stdin: "inherit",
|
|
@@ -57,3 +60,36 @@ function parseCommands(argv: string[]) {
|
|
|
57
60
|
const workerCommand = argv.slice(separator + 1);
|
|
58
61
|
return { apiCommand, workerCommand: workerCommand.length > 0 ? workerCommand : undefined };
|
|
59
62
|
}
|
|
63
|
+
|
|
64
|
+
function temporalEnabled(env: Record<string, string | undefined>) {
|
|
65
|
+
return (env.TEMPORAL_ENABLED ?? "true").trim().toLowerCase() !== "false";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function waitForTemporal(address: string) {
|
|
69
|
+
const { host, port } = parseTemporalAddress(address);
|
|
70
|
+
const deadline = Date.now() + 120_000;
|
|
71
|
+
|
|
72
|
+
while (Date.now() < deadline) {
|
|
73
|
+
const exitCode = await Bun.spawn(["nc", "-z", host, String(port)], {
|
|
74
|
+
stdin: "ignore",
|
|
75
|
+
stdout: "ignore",
|
|
76
|
+
stderr: "ignore",
|
|
77
|
+
}).exited;
|
|
78
|
+
if (exitCode === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await Bun.sleep(2_000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(`Temporal did not become ready at ${host}:${port} within 120 seconds`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseTemporalAddress(address: string) {
|
|
88
|
+
const trimmed = address.trim();
|
|
89
|
+
const withoutScheme = trimmed.includes("://") ? new URL(trimmed).host : trimmed;
|
|
90
|
+
const [host = "localhost", port = "7233"] = withoutScheme.split(":");
|
|
91
|
+
return {
|
|
92
|
+
host: host || "localhost",
|
|
93
|
+
port: Number(port || "7233"),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export async function ensureLocalPostgres() {
|
|
2
2
|
await ensureDockerRunning();
|
|
3
|
-
await run(["docker", "compose", "up", "-d"], { label: "start local
|
|
3
|
+
await run(["docker", "compose", "up", "-d"], { label: "start local services" });
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
async function ensureDockerRunning() {
|