create-svc 0.1.10 → 0.1.11

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.
Files changed (168) hide show
  1. package/README.md +46 -43
  2. package/bin/create-service.mjs +2 -0
  3. package/package.json +12 -9
  4. package/src/cli.test.ts +28 -10
  5. package/src/cli.ts +195 -30
  6. package/src/git-bootstrap.test.ts +40 -0
  7. package/src/git-bootstrap.ts +110 -0
  8. package/src/naming.test.ts +1 -0
  9. package/src/naming.ts +23 -0
  10. package/src/post-scaffold.test.ts +19 -0
  11. package/src/post-scaffold.ts +17 -4
  12. package/src/profiles.ts +2 -5
  13. package/src/scaffold.test.ts +231 -40
  14. package/src/scaffold.ts +84 -29
  15. package/src/vault.test.ts +61 -1
  16. package/src/vault.ts +77 -15
  17. package/templates/shared/.github/workflows/ci.yml +2 -1
  18. package/templates/shared/.github/workflows/deploy.yml +2 -0
  19. package/templates/shared/README.md +124 -47
  20. package/templates/shared/grafana/alerts.yaml +54 -0
  21. package/templates/shared/grafana/waitlist-dashboard.json +63 -0
  22. package/templates/shared/scripts/authctl.ts +231 -0
  23. package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
  24. package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
  25. package/templates/shared/scripts/cloudrun/cli.ts +324 -7
  26. package/templates/shared/scripts/cloudrun/config.ts +11 -4
  27. package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
  28. package/templates/shared/scripts/cloudrun/lib.ts +174 -41
  29. package/templates/shared/scripts/cloudrun/neon.ts +45 -0
  30. package/templates/shared/scripts/dev.ts +22 -0
  31. package/templates/shared/scripts/ensure-local-db.ts +3 -0
  32. package/templates/shared/scripts/local-docker.ts +63 -0
  33. package/templates/shared/scripts/local-env.ts +27 -0
  34. package/templates/shared/scripts/seed.ts +73 -0
  35. package/templates/shared/scripts/wait-for-db.ts +32 -0
  36. package/templates/shared/service.config.ts +59 -0
  37. package/templates/shared/service.yaml +24 -44
  38. package/templates/targets/workers/.github/workflows/ci.yml +19 -0
  39. package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
  40. package/templates/targets/workers/Makefile +33 -0
  41. package/templates/targets/workers/README.md +75 -0
  42. package/templates/targets/workers/package.json +35 -0
  43. package/templates/targets/workers/scripts/workers/cli.ts +397 -0
  44. package/templates/targets/workers/src/auth.ts +178 -0
  45. package/templates/targets/workers/src/index.ts +198 -0
  46. package/templates/targets/workers/src/storage.ts +370 -0
  47. package/templates/targets/workers/test/app.test.ts +108 -0
  48. package/templates/targets/workers/tsconfig.json +11 -0
  49. package/templates/targets/workers/wrangler.toml +24 -0
  50. package/templates/variants/bun-connectrpc/Makefile +14 -8
  51. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  52. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
  53. package/templates/variants/bun-connectrpc/package.json +12 -5
  54. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  55. package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
  56. package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
  57. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  58. package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
  59. package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
  60. package/templates/variants/bun-connectrpc/src/index.ts +76 -176
  61. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  62. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  63. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  64. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  65. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  66. package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
  67. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  68. package/templates/variants/bun-hono/Makefile +14 -8
  69. package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
  70. package/templates/variants/bun-hono/package.json +12 -5
  71. package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
  72. package/templates/variants/bun-hono/src/auth.ts +181 -0
  73. package/templates/variants/bun-hono/src/db/repository.ts +68 -421
  74. package/templates/variants/bun-hono/src/db/schema.ts +15 -64
  75. package/templates/variants/bun-hono/src/index.ts +65 -180
  76. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  77. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  78. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  79. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  80. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  81. package/templates/variants/bun-hono/test/app.test.ts +72 -41
  82. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  83. package/templates/variants/go-chi/Makefile +27 -11
  84. package/templates/variants/go-chi/atlas.hcl +8 -0
  85. package/templates/variants/go-chi/cmd/server/main.go +21 -10
  86. package/templates/variants/go-chi/go.mod +1 -3
  87. package/templates/variants/go-chi/internal/app/service.go +202 -685
  88. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  89. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  90. package/templates/variants/go-chi/internal/config/config.go +27 -11
  91. package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
  92. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  93. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  94. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  95. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  96. package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
  97. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  98. package/templates/variants/go-chi/package.json +7 -1
  99. package/templates/variants/go-connectrpc/Makefile +26 -9
  100. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  101. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
  102. package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
  103. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  104. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  105. package/templates/variants/go-connectrpc/go.mod +1 -1
  106. package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
  107. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  108. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  109. package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
  110. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
  111. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  112. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
  113. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  114. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  115. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  116. package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
  117. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  118. package/templates/variants/go-connectrpc/package.json +7 -1
  119. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  120. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  121. package/templates/root/.github/workflows/ci.yml +0 -26
  122. package/templates/root/.github/workflows/deploy.yml +0 -22
  123. package/templates/root/Dockerfile +0 -23
  124. package/templates/root/README.md +0 -69
  125. package/templates/root/buf.gen.yaml +0 -10
  126. package/templates/root/buf.yaml +0 -9
  127. package/templates/root/cmd/server/main.go +0 -44
  128. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  129. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  130. package/templates/root/go.mod +0 -10
  131. package/templates/root/internal/app/service.go +0 -152
  132. package/templates/root/internal/app/token_source.go +0 -50
  133. package/templates/root/internal/cloudflare/client.go +0 -160
  134. package/templates/root/internal/config/config.go +0 -55
  135. package/templates/root/internal/connectapi/handler.go +0 -79
  136. package/templates/root/internal/httpapi/routes.go +0 -93
  137. package/templates/root/internal/vault/client.go +0 -148
  138. package/templates/root/package.json +0 -12
  139. package/templates/root/protos/dns/v1/dns.proto +0 -58
  140. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  141. package/templates/root/scripts/cloudrun/config.ts +0 -50
  142. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  143. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  144. package/templates/root/service.yaml +0 -50
  145. package/templates/root/test/go.test.ts +0 -19
  146. package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
  147. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
  148. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
  149. package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
  150. package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
  151. package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
  152. package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
  153. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
  154. package/templates/variants/bun-hono/src/chat/service.ts +0 -384
  155. package/templates/variants/bun-hono/src/chat/types.ts +0 -142
  156. package/templates/variants/bun-hono/src/storage.ts +0 -72
  157. package/templates/variants/bun-hono/src/webhooks.ts +0 -35
  158. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
  159. package/templates/variants/go-chi/buf.gen.yaml +0 -12
  160. package/templates/variants/go-chi/buf.yaml +0 -9
  161. package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
  162. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
  163. package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
  164. package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
  165. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
  166. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
  167. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
  168. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
@@ -10,6 +10,7 @@ function baseConfig(overrides: Partial<ScaffoldConfig> = {}): ScaffoldConfig {
10
10
  directory: "svc",
11
11
  serviceName: "dns-api",
12
12
  modulePath: "example.com/dns-api",
13
+ target: "cloudrun",
13
14
  runtime: "bun",
14
15
  framework: "hono",
15
16
  region: "us-west1",
@@ -19,6 +20,11 @@ function baseConfig(overrides: Partial<ScaffoldConfig> = {}): ScaffoldConfig {
19
20
  billingAccount: "billingAccounts/01BD2E-3A6949-8F4C84",
20
21
  quotaProjectId: "anmho-infra-prod",
21
22
  profile: "microservice",
23
+ git: {
24
+ enabled: false,
25
+ owner: "anmho",
26
+ repository: "dns-api",
27
+ },
22
28
  neonDatabaseName: "dns_api",
23
29
  apiHostname: "api.dns-api.anmho.com",
24
30
  generatorRoot: join(import.meta.dir, ".."),
@@ -49,6 +55,14 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
49
55
  );
50
56
 
51
57
  const configScript = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "config.ts")).text();
58
+ const serviceConfig = await Bun.file(join(generatedRoot, "service.config.ts")).text();
59
+ expect(serviceConfig).toContain('service_id: "dns-api"');
60
+ expect(serviceConfig).toContain('target: "cloudrun"');
61
+ expect(serviceConfig).toContain('module: "buf.build/anmho/dns-api"');
62
+ expect(serviceConfig).toContain('issuer: "https://auth.anmho.com/api/auth"');
63
+ expect(serviceConfig).toContain('audience: "api://dns-api"');
64
+ expect(serviceConfig).toContain('vault_path_prefix: "prod/apps/dns-api/server/oauth-clients"');
65
+ expect(serviceConfig).toContain('api_key_secret_name: "dns-api-temporal-api-key"');
52
66
  expect(configScript).toContain('profile: "microservice"');
53
67
  expect(configScript).toContain('domain: "waitlist"');
54
68
  expect(configScript).toContain('kind: "microservice"');
@@ -56,30 +70,57 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
56
70
  expect(configScript).toContain(`framework: "${variant.framework}"`);
57
71
  expect(configScript).toContain('mode: "create_new"');
58
72
  expect(configScript).toContain('quotaProjectId: "anmho-infra-prod"');
73
+ expect(configScript).toContain('issuer: "https://auth.anmho.com/api/auth"');
74
+ expect(configScript).toContain('audience: "api://dns-api"');
75
+ expect(configScript).toContain('jwksUrl: "https://auth.anmho.com/api/auth/jwks"');
76
+ expect(configScript).toContain('apiKeySecretName: "dns-api-temporal-api-key"');
59
77
  expect(configScript).toContain('projectId: ""');
60
78
  expect(configScript).toContain('baseBranchId: ""');
61
79
  expect(configScript).toContain('baseBranchName: "main"');
62
80
  expect(configScript).toContain('previewBranchPrefix: "dns-api-pr"');
63
81
  expect(configScript).toContain('hostname: "api.dns-api.anmho.com"');
64
- expect(configScript).toContain('attachmentBucket: "anmho-dns-api-dns-api-attachments"');
65
- expect(configScript).toContain('attachmentPublicBaseUrl: "https://storage.googleapis.com/anmho-dns-api-dns-api-attachments"');
66
82
  expect(configScript).not.toContain("github:");
83
+ expect(configScript).not.toContain("attachmentBucket");
67
84
 
68
85
  const deployScript = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "lib.ts")).text();
69
86
  expect(deployScript).toContain('--billing-project", config.project.quotaProjectId');
87
+ expect(deployScript).toContain('config.project.mode === "use_existing"');
70
88
  expect(deployScript).toContain("serviceDomain");
71
89
  expect(deployScript).toContain("ensureProductionDomainMapping");
72
- expect(deployScript).toContain("ensureStorageBucket");
90
+ expect(deployScript).toContain('"domain-mappings",');
91
+ expect(deployScript).toContain('"--region",');
92
+ expect(deployScript).toContain("assertProductionDomainAvailable");
93
+ expect(deployScript).toContain("assertServiceNameAvailable");
94
+ expect(deployScript).not.toContain("ensureStorageBucket");
73
95
 
74
- const bootstrapScript = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "bootstrap.ts")).text();
75
- expect(bootstrapScript).toContain("publishProviderRuntimeSecrets");
96
+ expect(await Bun.file(join(generatedRoot, "scripts", "cloudrun", "integrations.ts")).exists()).toBeFalse();
97
+ const destroyScript = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "cleanup.ts")).text();
98
+ expect(destroyScript).toContain("assertOwnedResource");
99
+ expect(destroyScript).toContain("assertProductionDomainMappingOwned");
100
+ expect(destroyScript).toContain("deleteGrafanaResources");
101
+ expect(destroyScript).toContain('gcx", ["resources", "delete"');
102
+ expect(destroyScript).toContain("config.temporal.apiKeySecretName");
103
+ const neonScript = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "neon.ts")).text();
104
+ expect(neonScript).toContain("assertDatabaseOwned");
105
+ expect(neonScript).toContain("assertDisposableBranchName");
106
+ const seedScript = await Bun.file(join(generatedRoot, "scripts", "seed.ts")).text();
107
+ expect(seedScript).toContain("SEED_PROD=true");
108
+ expect(seedScript).toContain("waitlist_entries");
76
109
 
77
110
  const manifest = await Bun.file(join(generatedRoot, "service.yaml")).text();
78
- expect(manifest).toContain("CLERK_SECRET_KEY");
79
- expect(manifest).toContain("STRIPE_SECRET_KEY");
80
- expect(manifest).toContain("REVENUECAT_API_KEY");
81
- expect(manifest).toContain("RESEND_API_KEY");
82
- expect(manifest).toContain("POSTHOG_API_KEY");
111
+ expect(manifest).toContain("DATABASE_URL");
112
+ expect(manifest).toContain("TEMPORAL_ENABLED");
113
+ expect(manifest).toContain("TEMPORAL_TASK_QUEUE");
114
+ expect(manifest).toContain("TEMPORAL_API_KEY_ENV");
115
+ expect(manifest).toContain("AUTH_ENABLED");
116
+ expect(manifest).toContain("${AUTH_AUDIENCE}");
117
+ expect(manifest).toContain("managed_by: create-service");
118
+ expect(manifest).toContain("service_id: ${SERVICE_ID}");
119
+ expect(manifest).not.toContain("CLERK_SECRET_KEY");
120
+ expect(manifest).not.toContain("STRIPE_SECRET_KEY");
121
+ expect(manifest).not.toContain("REVENUECAT_API_KEY");
122
+ expect(manifest).not.toContain("RESEND_API_KEY");
123
+ expect(manifest).not.toContain("POSTHOG_API_KEY");
83
124
 
84
125
  const gitignore = await Bun.file(join(generatedRoot, ".gitignore")).text();
85
126
  expect(gitignore).toContain("node_modules");
@@ -90,59 +131,138 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
90
131
  expect(dockerCompose).toContain(`127.0.0.1:${localPort}:5432`);
91
132
 
92
133
  const envExample = await Bun.file(join(generatedRoot, ".env.example")).text();
93
- expect(envExample).toContain(`DATABASE_URL=postgres://postgres:postgres@127.0.0.1:${localPort}/dns_api`);
94
- expect(envExample).toContain("ATTACHMENT_BUCKET=dns-api-local-attachments");
95
- expect(envExample).toContain("CLERK_SECRET_KEY=");
96
- expect(envExample).toContain("STRIPE_SECRET_KEY=");
97
- expect(envExample).toContain("REVENUECAT_API_KEY=");
98
- expect(envExample).toContain("RESEND_API_KEY=");
99
- expect(envExample).toContain("POSTHOG_API_KEY=");
134
+ expect(envExample).toContain(`DATABASE_URL=postgres://postgres:postgres@127.0.0.1:${localPort}/dns_api?sslmode=disable`);
135
+ expect(envExample).toContain("AUTH_ENABLED=false");
136
+ expect(envExample).toContain("AUTH_AUDIENCE=api://dns-api");
137
+ expect(envExample).toContain("CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID=");
138
+ expect(envExample).toContain("TEMPORAL_API_KEY=");
139
+ expect(envExample).toContain("The base waitlist service does not require");
140
+ expect(envExample).not.toContain("ATTACHMENT_BUCKET=");
141
+ expect(envExample).not.toContain("CLERK_SECRET_KEY=");
142
+ expect(envExample).not.toContain("STRIPE_SECRET_KEY=");
143
+ expect(envExample).not.toContain("REVENUECAT_API_KEY=");
144
+ expect(envExample).not.toContain("RESEND_API_KEY=");
145
+ expect(envExample).not.toContain("POSTHOG_API_KEY=");
100
146
 
101
147
  const localEnv = await Bun.file(join(generatedRoot, ".env.local")).text();
102
- expect(localEnv).toContain(`DATABASE_URL=postgres://postgres:postgres@127.0.0.1:${localPort}/dns_api`);
103
- expect(localEnv).toContain("ATTACHMENT_PUBLIC_BASE_URL=https://storage.local.invalid/dns-api-local-attachments");
148
+ expect(localEnv).toContain(`DATABASE_URL=postgres://postgres:postgres@127.0.0.1:${localPort}/dns_api?sslmode=disable`);
149
+ expect(localEnv).not.toContain("ATTACHMENT_PUBLIC_BASE_URL=");
104
150
 
105
- expect(await Bun.file(join(generatedRoot, ".github", "workflows", "personal.yml")).exists()).toBeFalse();
151
+ const ciWorkflow = await Bun.file(join(generatedRoot, ".github", "workflows", "ci.yml")).text();
152
+ expect(ciWorkflow).toContain("bun run dashboards");
153
+ expect(ciWorkflow).toContain("GCX_ENABLED");
154
+ expect(await Bun.file(join(generatedRoot, "grafana", "waitlist-dashboard.json")).exists()).toBeTrue();
155
+ expect(await Bun.file(join(generatedRoot, "grafana", "alerts.yaml")).exists()).toBeTrue();
106
156
 
107
157
  if (variant.runtime === "go") {
108
158
  const goMod = await Bun.file(join(generatedRoot, "go.mod")).text();
109
- expect(goMod).toContain("connectrpc.com/connect");
110
159
  expect(goMod).toContain("module example.com/dns-api");
111
160
  expect(goMod).not.toContain("module github.com/anmho/dns-api");
112
161
 
113
162
  const mainGo = await Bun.file(join(generatedRoot, "cmd", "server", "main.go")).text();
114
- expect(mainGo).toContain("NewChatService");
115
163
  expect(mainGo).toContain("example.com/dns-api");
164
+ if (variant.framework === "connectrpc") {
165
+ expect(goMod).toContain("connectrpc.com/connect");
166
+ expect(mainGo).toContain("NewWaitlistService");
167
+ expect(mainGo).toContain("WaitlistServiceName");
168
+ } else {
169
+ expect(goMod).not.toContain("connectrpc.com/connect");
170
+ expect(mainGo).toContain("NewWaitlistService");
171
+ const routes = await Bun.file(join(generatedRoot, "internal", "httpapi", "routes.go")).text();
172
+ expect(routes).toContain("/v1/waitlist");
173
+ expect(routes).toContain("/v1/admin/waitlist");
174
+ expect(routes).toContain("/v1/triggers/waitlist");
175
+ expect(await Bun.file(join(generatedRoot, "buf.yaml")).exists()).toBeFalse();
176
+ }
177
+ expect(mainGo).toContain("internal/auth");
178
+ expect(mainGo).toContain("cfg.AuthAudience");
179
+ expect(mainGo).toContain("cfg.TemporalAPIKey");
180
+ expect(await Bun.file(join(generatedRoot, "internal", "auth", "middleware.go")).exists()).toBeTrue();
181
+ const makefile = await Bun.file(join(generatedRoot, "Makefile")).text();
182
+ expect(makefile).toContain("$(ATLAS) migrate apply --env local");
183
+ expect(makefile).toContain("$(ATLAS) migrate lint --env local --latest 1");
184
+ expect(makefile).toContain("bun run ./scripts/ensure-local-db.ts");
185
+ expect(makefile).toContain("bun run ./scripts/wait-for-db.ts");
186
+ expect(makefile).toContain("bun run ./scripts/dev.ts go run ./cmd/server");
187
+ expect(await Bun.file(join(generatedRoot, "atlas.hcl")).exists()).toBeTrue();
188
+ expect(await Bun.file(join(generatedRoot, "migrations", "atlas.sum")).exists()).toBeTrue();
189
+ expect(await Bun.file(join(generatedRoot, "cmd", "migrate", "main.go")).exists()).toBeFalse();
190
+ expect(await Bun.file(join(generatedRoot, "internal", "temporal", "worker.go")).exists()).toBeTrue();
191
+ expect(goMod).toContain("go.temporal.io/sdk");
116
192
  } else {
117
193
  const packageJson = await Bun.file(join(generatedRoot, "package.json")).text();
118
- expect(packageJson).toContain('"svc-cloudrun": "./scripts/cloudrun/cli.ts"');
119
- expect(packageJson).toContain('"dev": "bun run ./src/index.ts"');
194
+ expect(packageJson).toContain('"@anmho/authctl": "0.1.1"');
195
+ expect(packageJson).toContain("@temporalio/worker");
196
+ expect(packageJson).toContain('"service": "./scripts/cloudrun/cli.ts"');
197
+ expect(packageJson).toContain('"dev": "bun run ./scripts/dev.ts bun run ./src/index.ts"');
120
198
  expect(packageJson).toContain('"gen": "bun run ./scripts/codegen.ts"');
121
- expect(packageJson).toContain('"bootstrap": "bun run ./scripts/cloudrun/cli.ts bootstrap"');
199
+ expect(packageJson).toContain('"create": "bun run ./scripts/cloudrun/cli.ts create"');
122
200
  expect(packageJson).toContain('"deploy": "bun run ./scripts/cloudrun/cli.ts deploy"');
123
- expect(packageJson).toContain('"cleanup": "bun run ./scripts/cloudrun/cli.ts cleanup"');
201
+ expect(packageJson).toContain('"dashboards": "bun run ./scripts/cloudrun/cli.ts dashboards"');
202
+ expect(packageJson).toContain('"auth": "bun run ./scripts/cloudrun/cli.ts auth"');
203
+ expect(packageJson).toContain('"destroy": "bun run ./scripts/cloudrun/cli.ts destroy"');
204
+ const serviceCli = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "cli.ts")).text();
205
+ expect(serviceCli).toContain("service <create|deploy|migrate|seed|dashboards|dns|doctor|destroy|auth|sdk>");
206
+ expect(serviceCli).toContain("assertServiceNameAvailable(config.serviceName)");
207
+ expect(serviceCli).toContain("ensureAuthResourceServer");
208
+ expect(serviceCli).toContain('["resources", "push", "--path", "./grafana"]');
209
+ const cloudrunLib = await Bun.file(join(generatedRoot, "scripts", "cloudrun", "lib.ts")).text();
210
+ expect(cloudrunLib).toContain("resolveTemporalRuntimeConfig");
211
+ expect(cloudrunLib).toContain("TEMPORAL_API_KEY_ENV");
212
+ expect(cloudrunLib).toContain("value === undefined");
213
+
214
+ const authctlScript = await Bun.file(join(generatedRoot, "scripts", "authctl.ts")).text();
215
+ expect(authctlScript).toContain("authctl");
216
+ expect(authctlScript).toContain("resource-servers");
217
+ expect(authctlScript).toContain("clients");
218
+ expect(authctlScript).toContain("defaultClientTargetArgs");
219
+ expect(authctlScript).toContain('existsSync("./node_modules/.bin/authctl") ? "./node_modules/.bin/authctl" : Bun.which("authctl")');
220
+ expect(authctlScript).not.toContain('defaultAuthResourceServerArgs(), "--yes", "--json"');
221
+ const authScript = await Bun.file(join(generatedRoot, "src", "auth.ts")).text();
222
+ expect(authScript).toContain('"Ed25519"');
124
223
 
125
224
  const makefile = await Bun.file(join(generatedRoot, "Makefile")).text();
126
- expect(makefile).toContain("npx --no-install svc-cloudrun");
225
+ expect(makefile).toContain("npx --no-install service");
226
+ expect(makefile).toContain("dashboards:");
227
+ expect(makefile).toContain("auth:");
228
+ expect(makefile).toContain("bun run dev");
229
+ const devScript = await Bun.file(join(generatedRoot, "scripts", "dev.ts")).text();
230
+ expect(devScript).toContain("ensureLocalPostgres");
231
+ const localDocker = await Bun.file(join(generatedRoot, "scripts", "local-docker.ts")).text();
232
+ expect(localDocker).toContain('["docker", "info"]');
233
+ expect(localDocker).toContain('["open", "-a", "Docker"]');
234
+ expect(localDocker).toContain('["docker", "compose", "up", "-d"]');
127
235
 
128
236
  const entrypoint = await Bun.file(join(generatedRoot, "src", "index.ts")).text();
237
+ expect(await Bun.file(join(generatedRoot, "src", "auth.ts")).exists()).toBeTrue();
238
+ expect(entrypoint).toContain(variant.framework === "hono" ? 'app.use("/v1/*", authMiddleware())' : "withServiceAuth");
239
+ expect(await Bun.file(join(generatedRoot, "src", "temporal", "worker.ts")).exists()).toBeTrue();
129
240
  const readme = await Bun.file(join(generatedRoot, "README.md")).text();
130
241
  if (variant.framework === "connectrpc") {
131
- expect(entrypoint).toContain("ChatService");
132
- expect(gitignore).toContain("gen/");
242
+ expect(entrypoint).toContain("WaitlistService");
243
+ expect(gitignore).not.toContain("gen/");
133
244
  expect(readme).toContain("Local introspection");
134
245
  } else {
135
- expect(entrypoint).toContain("/v1/conversations");
246
+ expect(entrypoint).toContain("/v1/waitlist");
247
+ expect(entrypoint).toContain("/v1/admin/waitlist");
248
+ expect(entrypoint).toContain("/v1/triggers/waitlist");
136
249
  expect(gitignore).not.toContain("gen/");
137
250
  expect(readme).not.toContain("Local introspection");
138
251
  }
139
252
  expect(entrypoint).toContain(variant.framework === "hono" ? "Hono" : "connectNodeAdapter");
140
- expect(readme).toContain("ATTACHMENT_BUCKET");
141
253
  expect(readme).toContain("/webhooks/:provider");
142
254
  expect(readme).toContain("microservice profile");
143
255
  expect(readme).toContain("waitlist/launch service");
256
+ expect(readme).toContain("resource=api://<resource_server_id>");
144
257
  expect(readme).toContain("Terraform is optional");
258
+ expect(readme).toContain("AUTH_ENABLED=true");
259
+ expect(readme).toContain("verifies JWT bearer tokens");
260
+ expect(readme).toContain("prod/apps/auth/authctl/cloudflare-access");
261
+ expect(readme).toContain(variant.runtime === "bun" ? "bun run auth -- resource-server" : 'make auth ARGS="resource-server"');
145
262
  }
263
+
264
+ const deployWorkflow = await Bun.file(join(generatedRoot, ".github", "workflows", "deploy.yml")).text();
265
+ expect(deployWorkflow).toContain("bun run dashboards");
146
266
  }
147
267
  }, 30000);
148
268
 
@@ -161,30 +281,101 @@ test("scaffolds a backend package cleanly into a nested monorepo-style directory
161
281
  const readme = await Bun.file(join(generatedRoot, "README.md")).text();
162
282
  expect(readme).toContain("`microservice` profile");
163
283
  expect(readme).toContain("api.dns-api.anmho.com");
164
- expect(readme).toContain("docker compose up -d");
284
+ expect(readme).toContain("open Docker Desktop if needed");
165
285
  expect(readme).toContain("local Postgres service in `docker-compose.yml`");
166
286
  expect(readme).toContain("gcloud auth login");
167
287
  expect(readme).toContain("known-good CLIs");
168
- expect(readme).toContain("bun run bootstrap");
288
+ expect(readme).toContain("bun run create");
169
289
  expect(readme).toContain("bun run deploy");
170
- expect(readme).toContain("ATTACHMENT_BUCKET");
171
- expect(readme).toContain("one-command production bootstrap");
290
+ expect(readme).toContain("one-command production create");
172
291
  expect(readme).toContain("waitlist/launch service");
173
292
  expect(readme).toContain("Terraform is optional");
174
- expect(readme).toContain("webhook_events");
293
+ expect(readme).toContain("waitlist/launch service");
175
294
  expect(readme).not.toContain("Neon main, preview, and personal branch provisioning");
176
- expect(readme).not.toContain("GitHub Actions");
295
+ const ciWorkflow = await Bun.file(join(generatedRoot, ".github", "workflows", "ci.yml")).text();
296
+ expect(ciWorkflow).toContain("bun run dashboards");
177
297
  expect(readme).not.toContain("repository");
178
298
 
179
299
  const packageJson = await Bun.file(join(generatedRoot, "package.json")).text();
180
300
  expect(packageJson).toContain('"hono"');
181
301
 
182
302
  const entrypoint = await Bun.file(join(generatedRoot, "src", "index.ts")).text();
183
- expect(entrypoint).toContain("/v1/attachments/uploads");
184
-
185
- expect(await Bun.file(join(generatedRoot, ".github", "workflows", "ci.yml")).exists()).toBeFalse();
303
+ expect(entrypoint).toContain("/v1/waitlist");
304
+ expect(entrypoint).toContain("/v1/admin/waitlist");
186
305
  }, 15000);
187
306
 
307
+ test("scaffolds the workers target with wrangler lifecycle commands", async () => {
308
+ const root = await mkdtemp(join(tmpdir(), "create-svc-workers-"));
309
+ const generatedRoot = join(root, "edge-api");
310
+
311
+ await scaffoldProject(
312
+ baseConfig({
313
+ directory: generatedRoot,
314
+ target: "workers",
315
+ runtime: "bun",
316
+ framework: "hono",
317
+ })
318
+ );
319
+
320
+ const packageJson = await Bun.file(join(generatedRoot, "package.json")).text();
321
+ expect(packageJson).toContain('"@anmho/authctl": "0.1.1"');
322
+ expect(packageJson).toContain('"service": "./scripts/workers/cli.ts"');
323
+ expect(packageJson).toContain('"dev": "wrangler dev"');
324
+ expect(packageJson).toContain('"auth": "bun run ./scripts/workers/cli.ts auth"');
325
+ expect(packageJson).toContain('"wrangler"');
326
+ expect(packageJson).toContain('"pg"');
327
+
328
+ const wranglerConfig = await Bun.file(join(generatedRoot, "wrangler.toml")).text();
329
+ expect(wranglerConfig).toContain('name = "dns-api"');
330
+ expect(wranglerConfig).toContain('compatibility_flags = ["nodejs_compat"]');
331
+ expect(wranglerConfig).toContain('pattern = "api.dns-api.anmho.com/*"');
332
+ expect(wranglerConfig).toContain('binding = "HYPERDRIVE"');
333
+ expect(wranglerConfig).toContain('AUTH_ENABLED = "true"');
334
+ expect(wranglerConfig).toContain('AUTH_AUDIENCE = "api://dns-api"');
335
+
336
+ const entrypoint = await Bun.file(join(generatedRoot, "src", "index.ts")).text();
337
+ expect(entrypoint).toContain("/v1/waitlist");
338
+ expect(entrypoint).toContain("/v1/admin/waitlist");
339
+ expect(entrypoint).toContain('app.use("/v1/*", authMiddleware())');
340
+ expect(entrypoint).toContain("createStorage(context.env)");
341
+ expect(entrypoint).toContain("scheduled");
342
+ const readme = await Bun.file(join(generatedRoot, "README.md")).text();
343
+ expect(readme).toContain("Cloudflare Workers");
344
+ expect(readme).toContain("Hyperdrive binding for Neon-backed Postgres persistence");
345
+ expect(readme).not.toContain("Cloud Run");
346
+ const workerCli = await Bun.file(join(generatedRoot, "scripts", "workers", "cli.ts")).text();
347
+ expect(workerCli).toContain("hyperdrive");
348
+ expect(workerCli).toContain('["resources", "push", "--path", "./grafana"]');
349
+ expect(workerCli).toContain("ensureAuthResourceServer");
350
+ expect(workerCli).toContain("Workers database schema applied");
351
+ expect(workerCli).toContain("create table if not exists waitlist_entries");
352
+ expect(workerCli).toContain("DATABASE_URL or NEON_API_KEY is required to provision the Hyperdrive binding");
353
+ expect(workerCli).toContain("createProjectBranchDatabase");
354
+ expect(workerCli).toContain("deleteNeonDatabase");
355
+ expect(workerCli).toContain("deleteGrafanaResources");
356
+ expect(workerCli).toContain("hyperdrive\", \"delete");
357
+ const makefile = await Bun.file(join(generatedRoot, "Makefile")).text();
358
+ expect(makefile).toContain('no generated code for workers');
359
+ expect(makefile).toContain("auth:");
360
+ expect(makefile).not.toContain("scripts/codegen.ts");
361
+
362
+ expect(await Bun.file(join(generatedRoot, "scripts", "authctl.ts")).exists()).toBeTrue();
363
+ expect(await Bun.file(join(generatedRoot, "src", "auth.ts")).exists()).toBeTrue();
364
+ expect(await Bun.file(join(generatedRoot, "src", "storage.ts")).exists()).toBeTrue();
365
+ expect(await Bun.file(join(generatedRoot, "scripts", "workers", "cli.ts")).exists()).toBeTrue();
366
+ expect(await Bun.file(join(generatedRoot, "scripts", "cloudrun", "cli.ts")).exists()).toBeFalse();
367
+ expect(await Bun.file(join(generatedRoot, "scripts", "dev.ts")).exists()).toBeFalse();
368
+ expect(await Bun.file(join(generatedRoot, "scripts", "ensure-local-db.ts")).exists()).toBeFalse();
369
+ expect(await Bun.file(join(generatedRoot, "scripts", "local-docker.ts")).exists()).toBeFalse();
370
+ expect(await Bun.file(join(generatedRoot, "scripts", "wait-for-db.ts")).exists()).toBeFalse();
371
+ expect(await Bun.file(join(generatedRoot, "service.yaml")).exists()).toBeFalse();
372
+ expect(await Bun.file(join(generatedRoot, "Dockerfile")).exists()).toBeFalse();
373
+ expect(await Bun.file(join(generatedRoot, "docker-compose.yml")).exists()).toBeFalse();
374
+ expect(await Bun.file(join(generatedRoot, "src", "db", "repository.ts")).exists()).toBeFalse();
375
+ expect(await Bun.file(join(generatedRoot, "src", "temporal", "worker.ts")).exists()).toBeFalse();
376
+ expect(await Bun.file(join(generatedRoot, "scripts", "codegen.ts")).exists()).toBeFalse();
377
+ });
378
+
188
379
  test("microservice profile does not generate a website package", async () => {
189
380
  const root = await mkdtemp(join(tmpdir(), "create-svc-microservice-profile-"));
190
381
  const generatedRoot = join(root, "service");
package/src/scaffold.ts CHANGED
@@ -4,16 +4,19 @@ import {
4
4
  compactIdentifier,
5
5
  compactDatabaseName,
6
6
  deriveLocalPostgresPort,
7
+ type DeployTarget,
7
8
  type Framework,
8
9
  type GcpProjectMode,
9
10
  type Runtime,
10
11
  } from "./naming";
11
12
  import { exampleForProfile, type Profile } from "./profiles";
13
+ import type { GitBootstrapConfig } from "./git-bootstrap";
12
14
 
13
15
  export type ScaffoldConfig = {
14
16
  directory: string;
15
17
  serviceName: string;
16
18
  modulePath: string;
19
+ target: DeployTarget;
17
20
  runtime: Runtime;
18
21
  framework: Framework;
19
22
  profile: Profile;
@@ -24,6 +27,7 @@ export type ScaffoldConfig = {
24
27
  billingAccount: string;
25
28
  quotaProjectId: string;
26
29
  autoDeploy: boolean;
30
+ git: GitBootstrapConfig;
27
31
  neonDatabaseName: string;
28
32
  apiHostname: string;
29
33
  generatorRoot: string;
@@ -46,15 +50,20 @@ export async function scaffoldProject(config: ScaffoldConfig) {
46
50
  await ensureTargetDirectory(targetDir);
47
51
 
48
52
  const replacements = buildReplacements(config);
49
- const sharedTemplateRoot = resolve(config.generatorRoot, "templates", "shared");
50
- const variantTemplateRoot = resolve(config.generatorRoot, "templates", "variants", `${config.runtime}-${config.framework}`);
51
- const templateRoots = [sharedTemplateRoot, variantTemplateRoot];
53
+ const templateRoots = [
54
+ { kind: "shared" as const, root: resolve(config.generatorRoot, "templates", "shared") },
55
+ { kind: "variant" as const, root: resolve(config.generatorRoot, "templates", "variants", `${config.runtime}-${config.framework}`) },
56
+ { kind: "target" as const, root: resolve(config.generatorRoot, "templates", "targets", config.target) },
57
+ ];
52
58
 
53
- for (const templateRoot of templateRoots) {
54
- const files = await collectTemplateFiles(templateRoot);
59
+ for (const template of templateRoots) {
60
+ const files = await collectTemplateFiles(template.root);
55
61
 
56
62
  for (const relativePath of files) {
57
- const sourcePath = join(templateRoot, relativePath);
63
+ if (shouldSkipForTarget(config.target, template.kind, relativePath)) {
64
+ continue;
65
+ }
66
+ const sourcePath = join(template.root, relativePath);
58
67
  const destinationPath = join(targetDir, relativePath);
59
68
  const raw = await Bun.file(sourcePath).text();
60
69
  const rendered = renderTemplate(raw, replacements);
@@ -67,6 +76,43 @@ export async function scaffoldProject(config: ScaffoldConfig) {
67
76
  await writeLocalEnvFile(targetDir, replacements);
68
77
  }
69
78
 
79
+ function shouldSkipForTarget(target: DeployTarget, templateKind: "shared" | "variant" | "target", relativePath: string) {
80
+ if (target === "workers") {
81
+ if (templateKind === "target") {
82
+ return false;
83
+ }
84
+
85
+ if (relativePath === "Dockerfile" || relativePath === "docker-compose.yml") {
86
+ return true;
87
+ }
88
+
89
+ if (templateKind === "shared") {
90
+ return (
91
+ relativePath === "service.yaml" ||
92
+ relativePath === "scripts/dev.ts" ||
93
+ relativePath === "scripts/ensure-local-db.ts" ||
94
+ relativePath === "scripts/local-docker.ts" ||
95
+ relativePath === "scripts/local-env.ts" ||
96
+ relativePath === "scripts/seed.ts" ||
97
+ relativePath === "scripts/wait-for-db.ts" ||
98
+ relativePath.startsWith("scripts/cloudrun/")
99
+ );
100
+ }
101
+
102
+ return (
103
+ relativePath.startsWith("src/db/") ||
104
+ relativePath.startsWith("src/temporal/") ||
105
+ relativePath.startsWith("src/waitlist/") ||
106
+ relativePath.startsWith("test/") ||
107
+ relativePath.startsWith("migrations/") ||
108
+ relativePath === "scripts/codegen.ts" ||
109
+ relativePath === "scripts/migrate.ts"
110
+ );
111
+ }
112
+
113
+ return relativePath.startsWith("scripts/workers/") || relativePath === "wrangler.toml";
114
+ }
115
+
70
116
  async function ensureTargetDirectory(targetDir: string) {
71
117
  await assertTargetDirectoryIsEmpty(targetDir);
72
118
  await mkdir(targetDir, { recursive: true });
@@ -88,21 +134,26 @@ export async function assertTargetDirectoryIsEmpty(targetDir: string) {
88
134
 
89
135
  async function collectTemplateFiles(root: string, relative = ""): Promise<string[]> {
90
136
  const cwd = join(root, relative);
91
- const entries = await readdir(cwd, { withFileTypes: true });
137
+ let entries;
138
+ try {
139
+ entries = await readdir(cwd, { withFileTypes: true });
140
+ } catch (error) {
141
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
142
+ return [];
143
+ }
144
+ throw error;
145
+ }
92
146
  const files: string[] = [];
93
147
 
94
148
  for (const entry of entries) {
95
149
  const nextRelative = relative ? join(relative, entry.name) : entry.name;
96
150
  if (entry.isDirectory()) {
97
- if (nextRelative === ".github" || nextRelative.startsWith(".github/")) {
151
+ if (entry.name === "node_modules" || entry.name === ".git") {
98
152
  continue;
99
153
  }
100
154
  files.push(...(await collectTemplateFiles(root, nextRelative)));
101
155
  continue;
102
156
  }
103
- if (nextRelative === ".github" || nextRelative.startsWith(".github/")) {
104
- continue;
105
- }
106
157
  files.push(nextRelative);
107
158
  }
108
159
 
@@ -115,16 +166,17 @@ function buildReplacements(config: ScaffoldConfig) {
115
166
  const runtimeServiceAccount = `${serviceAccountBase}-runtime@${config.gcpProject}.iam.gserviceaccount.com`;
116
167
  const previewBranchPrefix = `${config.serviceName}-pr`;
117
168
  const personalBranchPrefix = `${config.serviceName}-dev`;
118
- const remoteAttachmentBucket = `${config.gcpProject}-${config.serviceName}-attachments`;
119
- const remoteAttachmentPublicBaseUrl = `https://storage.googleapis.com/${remoteAttachmentBucket}`;
120
169
  const localDatabaseName = compactDatabaseName(config.serviceName);
121
170
  const localDatabasePort = deriveLocalPostgresPort(config.serviceName);
122
- const localAttachmentBucket = `${config.serviceName}-local-attachments`;
123
- const localAttachmentPublicBaseUrl = `https://storage.local.invalid/${localAttachmentBucket}`;
171
+ const authIssuer = "https://auth.anmho.com/api/auth";
172
+ const authAudience = `api://${config.serviceName}`;
173
+ const authJwksUrl = `${authIssuer}/jwks`;
124
174
 
125
175
  return {
126
176
  SERVICE_NAME: config.serviceName,
177
+ SERVICE_ID: config.serviceName,
127
178
  MODULE_PATH: config.modulePath,
179
+ TARGET: config.target,
128
180
  PROJECT_ID: config.gcpProject,
129
181
  PROJECT_NAME: config.gcpProjectName,
130
182
  REGION: config.region,
@@ -150,32 +202,37 @@ function buildReplacements(config: ScaffoldConfig) {
150
202
  RUNTIME_SERVICE_ACCOUNT: runtimeServiceAccount,
151
203
  API_HOSTNAME: config.apiHostname,
152
204
  API_BASE_DOMAIN: "anmho.com",
153
- ATTACHMENT_BUCKET: remoteAttachmentBucket,
154
- ATTACHMENT_PUBLIC_BASE_URL: remoteAttachmentPublicBaseUrl,
205
+ AUTH_ISSUER: authIssuer,
206
+ AUTH_AUDIENCE: authAudience,
207
+ AUTH_JWKS_URL: authJwksUrl,
155
208
  LOCAL_DATABASE_NAME: localDatabaseName,
156
209
  LOCAL_DATABASE_PORT: localDatabasePort,
157
210
  LOCAL_DATABASE_USER: "postgres",
158
211
  LOCAL_DATABASE_PASSWORD: "postgres",
159
- LOCAL_ATTACHMENT_BUCKET: localAttachmentBucket,
160
- LOCAL_ATTACHMENT_PUBLIC_BASE_URL: localAttachmentPublicBaseUrl,
161
212
  COMMAND_DEV: config.runtime === "bun" ? "bun run dev" : "make dev",
162
213
  COMMAND_MIGRATE: config.runtime === "bun" ? "bun run migrate" : "make migrate",
163
214
  COMMAND_GEN: config.runtime === "bun" ? "bun run gen" : "make gen",
164
215
  COMMAND_LINT: config.runtime === "bun" ? "bun run lint" : "make lint",
165
216
  COMMAND_TEST: config.runtime === "bun" ? "bun run test" : "make test",
166
- COMMAND_BOOTSTRAP: config.runtime === "bun" ? "bun run bootstrap" : "make bootstrap",
217
+ COMMAND_BOOTSTRAP: config.runtime === "bun" ? "bun run create" : "make create",
167
218
  COMMAND_DEPLOY: config.runtime === "bun" ? "bun run deploy" : "make deploy",
219
+ COMMAND_AUTH_RESOURCE:
220
+ config.runtime === "bun" ? "bun run auth -- resource-server" : 'make auth ARGS="resource-server"',
221
+ COMMAND_AUTH_CLIENT:
222
+ config.runtime === "bun"
223
+ ? "bun run auth -- client create"
224
+ : 'make auth ARGS="client create"',
168
225
  COMMAND_DEPLOY_PERSONAL:
169
226
  config.runtime === "bun"
170
227
  ? 'bun run deploy -- --environment personal --name <slug>'
171
228
  : 'make deploy ARGS="--environment personal --name <slug>"',
172
229
  COMMAND_DEPLOY_DESTROY:
173
230
  config.runtime === "bun"
174
- ? 'bun run deploy -- --destroy --environment personal --name <slug>'
175
- : 'make deploy ARGS="--destroy --environment personal --name <slug>"',
176
- COMMAND_CLEANUP: config.runtime === "bun" ? "bun run cleanup" : "make cleanup",
177
- COMMAND_CLEANUP_PROJECT: config.runtime === "bun" ? "bun run cleanup -- --project" : 'make cleanup ARGS="--project"',
178
- GITIGNORE_EXTRA: config.framework === "connectrpc" ? "gen/" : "",
231
+ ? 'bun run destroy -- --environment personal --name <slug>'
232
+ : 'make destroy ARGS="--environment personal --name <slug>"',
233
+ COMMAND_CLEANUP: config.runtime === "bun" ? "bun run destroy" : "make destroy",
234
+ COMMAND_CLEANUP_PROJECT: config.runtime === "bun" ? "bun run destroy -- --project" : 'make destroy ARGS="--project"',
235
+ GITIGNORE_EXTRA: "",
179
236
  LOCAL_INTROSPECTION_NOTE:
180
237
  config.framework === "connectrpc"
181
238
  ? [
@@ -200,12 +257,10 @@ async function writeLocalEnvFile(targetDir: string, replacements: Record<string,
200
257
 
201
258
  const rendered = renderTemplate(
202
259
  [
203
- "# Generated local development defaults for create-svc.",
260
+ "# Generated local development defaults for create-service.",
204
261
  "# This file is user-owned after scaffold and is gitignored.",
205
262
  "",
206
- "DATABASE_URL=postgres://{{LOCAL_DATABASE_USER}}:{{LOCAL_DATABASE_PASSWORD}}@127.0.0.1:{{LOCAL_DATABASE_PORT}}/{{LOCAL_DATABASE_NAME}}",
207
- "ATTACHMENT_BUCKET={{LOCAL_ATTACHMENT_BUCKET}}",
208
- "ATTACHMENT_PUBLIC_BASE_URL={{LOCAL_ATTACHMENT_PUBLIC_BASE_URL}}",
263
+ "DATABASE_URL=postgres://{{LOCAL_DATABASE_USER}}:{{LOCAL_DATABASE_PASSWORD}}@127.0.0.1:{{LOCAL_DATABASE_PORT}}/{{LOCAL_DATABASE_NAME}}?sslmode=disable",
209
264
  "",
210
265
  ].join("\n"),
211
266
  replacements