create-svc 0.1.11 → 0.1.13

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # create-service
1
+ # service
2
2
 
3
- `create-service` is a local microservice bootstrap CLI for generating standalone API services with:
3
+ `service` is a local microservice CLI for generating standalone API services and operating them after generation with the same command name.
4
4
 
5
5
  - a single `microservice` generation path
6
6
  - explicit deploy target selection: Cloud Run or Cloudflare Workers
@@ -8,7 +8,7 @@
8
8
  - HTTP frameworks (`chi` or `hono`) and ConnectRPC variants
9
9
  - standalone package output that does not assume repo bootstrap
10
10
  - a generated `service.config.ts` manifest
11
- - a generated `service` lifecycle CLI for create, deploy, migrate, seed, dashboards, doctor, and destroy
11
+ - one `service` CLI for scaffold, create, deploy, migrate, seed, dashboards, doctor, and destroy
12
12
  - local Docker Compose Postgres for first-run development
13
13
  - Neon-backed remote environments
14
14
  - a production API origin at `https://api.<service_id>.anmho.com`
@@ -20,25 +20,27 @@ npm: <https://www.npmjs.com/package/create-svc>
20
20
  ## Usage
21
21
 
22
22
  ```bash
23
- bun create svc my-service
23
+ service create my-service
24
24
  ```
25
25
 
26
- Compatibility alias:
26
+ Inside a generated service repo, the same command operates that repo:
27
27
 
28
28
  ```bash
29
- bun create svc my-service
29
+ cd my-service
30
+ service create
31
+ service deploy
30
32
  ```
31
33
 
32
- or:
34
+ To install from npm:
33
35
 
34
36
  ```bash
35
- bunx create-svc my-service
37
+ bun add -g create-svc
36
38
  ```
37
39
 
38
40
  For the strict one-command production path:
39
41
 
40
42
  ```bash
41
- bun create svc my-service --yes
43
+ service create my-service --yes
42
44
  ```
43
45
 
44
46
  `--profile microservice` is accepted as a compatibility no-op. App workspaces live outside this package in private app template repositories.
@@ -46,7 +48,7 @@ bun create svc my-service --yes
46
48
  By default, a standalone generated service is initialized as a git repository,
47
49
  committed with `Initial commit`, created as a private GitHub repository at
48
50
  `anmho/<service-name>`, and pushed to `origin/main`. If the target directory is
49
- inside an existing git worktree, create-service skips git and GitHub setup so the
51
+ inside an existing git worktree, `service` skips git and GitHub setup so the
50
52
  parent repository remains in control. Pass `--no-git` to skip all git and GitHub
51
53
  side effects.
52
54
 
@@ -57,15 +59,14 @@ Without publishing to npm:
57
59
  ```bash
58
60
  bun install
59
61
  npm pack
60
- bunx ./create-svc-*.tgz my-service
62
+ bunx ./create-svc-*.tgz create my-service
61
63
  ```
62
64
 
63
65
  For faster iteration against your working tree:
64
66
 
65
67
  ```bash
66
68
  bun link
67
- bun link create-service
68
- create-service my-service
69
+ service create my-service
69
70
  ```
70
71
 
71
72
  During scaffold, the generator can discover:
@@ -96,9 +97,9 @@ bun run dev
96
97
  bun run gen
97
98
  bun run lint
98
99
  bun run test
99
- bun run create
100
- bun run deploy
101
- bun run destroy
100
+ service create
101
+ service deploy
102
+ service destroy
102
103
  ```
103
104
 
104
105
  For Go variants:
@@ -109,9 +110,9 @@ make dev
109
110
  make gen
110
111
  make lint
111
112
  make test
112
- make create
113
- make deploy
114
- make destroy
113
+ service create
114
+ service deploy
115
+ service destroy
115
116
  ```
116
117
 
117
118
  Language-specific tasks such as local running, linting, formatting, testing, and building stay in package scripts or Make targets. Service lifecycle operations are exposed through the generated `service` CLI.
@@ -125,7 +126,7 @@ The generated microservice domain is a small waitlist/launch service example wit
125
126
  ```bash
126
127
  bun install
127
128
  bun test src scripts
128
- bun run index.ts my-service
129
+ bun run index.ts create my-service
129
130
  ```
130
131
 
131
132
  Validate the generated app matrix against local Docker Compose Postgres:
@@ -140,7 +141,7 @@ The validation harness scaffolds generated services into ignored `bin/generated/
140
141
 
141
142
  ## npm Trusted Publishing
142
143
 
143
- `create-service` is set up for npm trusted publishing from GitHub Actions, so there is no long-lived npm publish token to store in Vault.
144
+ `create-svc` is set up for npm trusted publishing from GitHub Actions, so there is no long-lived npm publish token to store in Vault.
144
145
 
145
146
  Repository workflow:
146
147
 
@@ -150,12 +151,12 @@ Repository workflow:
150
151
 
151
152
  npm package setup still has to be configured once in the npm UI to trust this repository and workflow:
152
153
 
153
- 1. Open the `create-service` package settings on npm.
154
+ 1. Open the `create-svc` package settings on npm.
154
155
  2. Go to `Settings` -> `Trusted Publisher`.
155
156
  3. Select `GitHub Actions`.
156
157
  4. Enter:
157
158
  - Organization or user: `anmho`
158
- - Repository: `create-service`
159
+ - Repository: `create-svc`
159
160
  - Workflow filename: `publish.yml`
160
161
  5. Save the trusted publisher.
161
162
 
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { run } from "./src/cli";
3
+ import { runServiceCommand } from "./src/service";
4
4
 
5
- await run(process.argv.slice(2));
5
+ await runServiceCommand(process.argv.slice(2));
package/package.json CHANGED
@@ -1,17 +1,15 @@
1
1
  {
2
2
  "name": "create-svc",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
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",
7
7
  "license": "MIT",
8
8
  "bin": {
9
- "create-service": "bin/create-service.mjs",
10
- "create-svc": "bin/create-svc.mjs"
9
+ "service": "bin/service.mjs"
11
10
  },
12
11
  "files": [
13
- "bin/create-service.mjs",
14
- "bin/create-svc.mjs",
12
+ "bin/service.mjs",
15
13
  "index.ts",
16
14
  "src",
17
15
  "templates",
package/src/cli.ts CHANGED
@@ -76,7 +76,7 @@ export async function run(argv: string[]) {
76
76
 
77
77
  await maybeCheckForUpdate(args);
78
78
 
79
- intro(`${pc.bold("create-service")} ${pc.dim("microservice bootstrap")}`);
79
+ intro(`${pc.bold("service")} ${pc.dim("microservice bootstrap")}`);
80
80
 
81
81
  const config = await resolveConfig(args);
82
82
  const targetDir = resolve(process.cwd(), config.directory);
@@ -137,13 +137,11 @@ export async function run(argv: string[]) {
137
137
  `Local DB: ${pc.cyan("started by local dev command")}`,
138
138
  `Migrate: ${pc.cyan(isBun ? "bun run migrate" : "make migrate")}`,
139
139
  `Local dev: ${pc.cyan(isBun ? "bun run dev" : "make dev")}`,
140
- `Create: ${pc.cyan(isBun ? "bun run service -- create" : "make create")}`,
141
- `Deploy: ${pc.cyan(isBun ? "bun run service -- deploy" : "make deploy")}`,
140
+ `Create: ${pc.cyan("service create")}`,
141
+ `Deploy: ${pc.cyan("service deploy")}`,
142
142
  config.git.enabled ? `Repository: ${pc.cyan(`https://github.com/anmho/${config.git.repository}`)}` : undefined,
143
143
  `Personal env: ${pc.cyan(
144
- isBun
145
- ? `bun run deploy -- --environment personal --name ${config.serviceName}`
146
- : `make deploy ARGS="--environment personal --name ${config.serviceName}"`
144
+ `service deploy --environment personal --name ${config.serviceName}`
147
145
  )}`,
148
146
  `Production API: ${pc.cyan(`https://${config.apiHostname}`)}`,
149
147
  ].filter(Boolean).join("\n")
@@ -827,7 +825,7 @@ export function validateServiceNameInput(rawValue: string, directoryOverride?: s
827
825
  function printHelp() {
828
826
  log.message(`
829
827
  Usage:
830
- create-service [service_id] [options]
828
+ service create [service_id] [options]
831
829
 
832
830
  Options:
833
831
  --target <cloudrun|workers> Deploy target for the generated service
@@ -258,7 +258,7 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
258
258
  expect(readme).toContain("AUTH_ENABLED=true");
259
259
  expect(readme).toContain("verifies JWT bearer tokens");
260
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"');
261
+ expect(readme).toContain("service auth resource-server");
262
262
  }
263
263
 
264
264
  const deployWorkflow = await Bun.file(join(generatedRoot, ".github", "workflows", "deploy.yml")).text();
@@ -285,8 +285,8 @@ test("scaffolds a backend package cleanly into a nested monorepo-style directory
285
285
  expect(readme).toContain("local Postgres service in `docker-compose.yml`");
286
286
  expect(readme).toContain("gcloud auth login");
287
287
  expect(readme).toContain("known-good CLIs");
288
- expect(readme).toContain("bun run create");
289
- expect(readme).toContain("bun run deploy");
288
+ expect(readme).toContain("service create");
289
+ expect(readme).toContain("service deploy");
290
290
  expect(readme).toContain("one-command production create");
291
291
  expect(readme).toContain("waitlist/launch service");
292
292
  expect(readme).toContain("Terraform is optional");
package/src/scaffold.ts CHANGED
@@ -214,24 +214,14 @@ function buildReplacements(config: ScaffoldConfig) {
214
214
  COMMAND_GEN: config.runtime === "bun" ? "bun run gen" : "make gen",
215
215
  COMMAND_LINT: config.runtime === "bun" ? "bun run lint" : "make lint",
216
216
  COMMAND_TEST: config.runtime === "bun" ? "bun run test" : "make test",
217
- COMMAND_BOOTSTRAP: config.runtime === "bun" ? "bun run create" : "make create",
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"',
225
- COMMAND_DEPLOY_PERSONAL:
226
- config.runtime === "bun"
227
- ? 'bun run deploy -- --environment personal --name <slug>'
228
- : 'make deploy ARGS="--environment personal --name <slug>"',
229
- COMMAND_DEPLOY_DESTROY:
230
- config.runtime === "bun"
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"',
217
+ COMMAND_BOOTSTRAP: "service create",
218
+ COMMAND_DEPLOY: "service deploy",
219
+ COMMAND_AUTH_RESOURCE: "service auth resource-server",
220
+ COMMAND_AUTH_CLIENT: "service auth client create",
221
+ COMMAND_DEPLOY_PERSONAL: "service deploy --environment personal --name <name>",
222
+ COMMAND_DEPLOY_DESTROY: "service destroy --environment personal --name <name>",
223
+ COMMAND_CLEANUP: "service destroy",
224
+ COMMAND_CLEANUP_PROJECT: "service destroy --project",
235
225
  GITIGNORE_EXTRA: "",
236
226
  LOCAL_INTROSPECTION_NOTE:
237
227
  config.framework === "connectrpc"
@@ -0,0 +1,30 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { findGeneratedServiceRoot, normalizeScaffoldArgs } from "./service";
6
+
7
+ test("normalizeScaffoldArgs treats service create as the scaffold command outside a service repo", () => {
8
+ expect(normalizeScaffoldArgs(["create", "launch-api", "--yes"])).toEqual(["launch-api", "--yes"]);
9
+ expect(normalizeScaffoldArgs(["new", "launch-api"])).toEqual(["launch-api"]);
10
+ expect(normalizeScaffoldArgs(["init", "launch-api"])).toEqual(["launch-api"]);
11
+ expect(normalizeScaffoldArgs(["launch-api", "--yes"])).toEqual(["launch-api", "--yes"]);
12
+ });
13
+
14
+ test("normalizeScaffoldArgs maps service help to generator help outside a service repo", () => {
15
+ expect(normalizeScaffoldArgs(["help"])).toEqual(["--help"]);
16
+ expect(normalizeScaffoldArgs(["help", "--verbose"])).toEqual(["--help", "--verbose"]);
17
+ });
18
+
19
+ test("findGeneratedServiceRoot detects generated service context from nested directories", async () => {
20
+ const root = await mkdtemp(join(tmpdir(), "create-svc-service-root-"));
21
+ const serviceRoot = join(root, "generated-api");
22
+ const nested = join(serviceRoot, "src", "waitlist");
23
+ await mkdir(join(serviceRoot, "scripts", "cloudrun"), { recursive: true });
24
+ await mkdir(nested, { recursive: true });
25
+ await writeFile(join(serviceRoot, "service.config.ts"), "export default {}");
26
+ await writeFile(join(serviceRoot, "scripts", "cloudrun", "cli.ts"), "");
27
+
28
+ expect(findGeneratedServiceRoot(nested)).toBe(serviceRoot);
29
+ expect(findGeneratedServiceRoot(root)).toBeUndefined();
30
+ });
package/src/service.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { run as runScaffoldCli } from "./cli";
4
+
5
+ const SCAFFOLD_COMMANDS = new Set(["create", "new", "init"]);
6
+
7
+ export async function runServiceCommand(argv: string[], cwd = process.cwd()) {
8
+ const serviceRoot = findGeneratedServiceRoot(cwd);
9
+ if (serviceRoot) {
10
+ delegateToGeneratedService(serviceRoot, argv);
11
+ return;
12
+ }
13
+
14
+ await runScaffoldCli(normalizeScaffoldArgs(argv));
15
+ }
16
+
17
+ export function normalizeScaffoldArgs(argv: string[]) {
18
+ const [command, ...rest] = argv;
19
+ if (command && SCAFFOLD_COMMANDS.has(command)) {
20
+ return rest;
21
+ }
22
+ if (command === "help") {
23
+ return ["--help", ...rest];
24
+ }
25
+ return argv;
26
+ }
27
+
28
+ export function findGeneratedServiceRoot(start: string): string | undefined {
29
+ let current = start;
30
+ while (true) {
31
+ if (isGeneratedServiceRoot(current)) {
32
+ return current;
33
+ }
34
+
35
+ const parent = dirname(current);
36
+ if (parent === current) {
37
+ return undefined;
38
+ }
39
+ current = parent;
40
+ }
41
+ }
42
+
43
+ function isGeneratedServiceRoot(path: string) {
44
+ return (
45
+ existsSync(join(path, "service.config.ts")) &&
46
+ (existsSync(join(path, "scripts", "cloudrun", "cli.ts")) || existsSync(join(path, "scripts", "workers", "cli.ts")))
47
+ );
48
+ }
49
+
50
+ function delegateToGeneratedService(serviceRoot: string, argv: string[]) {
51
+ const cliPath = existsSync(join(serviceRoot, "scripts", "cloudrun", "cli.ts"))
52
+ ? "./scripts/cloudrun/cli.ts"
53
+ : "./scripts/workers/cli.ts";
54
+ const result = Bun.spawnSync(["bun", "run", cliPath, ...argv], {
55
+ cwd: serviceRoot,
56
+ env: process.env,
57
+ stdin: "inherit",
58
+ stdout: "inherit",
59
+ stderr: "inherit",
60
+ });
61
+
62
+ if (!result.success) {
63
+ process.exit(result.exitCode || 1);
64
+ }
65
+ }
package/src/vault.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { afterEach, expect, mock, test } from "bun:test";
2
2
  import { mkdir } from "node:fs/promises";
3
- import { readVaultSecret, resolveNeonApiKey, upsertVaultSecretFields } from "./vault";
3
+ import { readVaultSecret, resolveNeonApiKey } from "./vault";
4
4
 
5
5
  const originalEnv = { ...process.env };
6
6
 
@@ -97,63 +97,3 @@ test("readVaultSecret falls back to ~/.vault-token", async () => {
97
97
  })
98
98
  ).resolves.toBe("vault-token");
99
99
  });
100
-
101
- test("upsertVaultSecretFields writes merged KV v2 data", async () => {
102
- process.env.VAULT_ADDR = "https://vault.example.com";
103
- process.env.VAULT_TOKEN = "token-123";
104
-
105
- const requests: Array<{ method: string; url: string; body?: unknown }> = [];
106
- const fetchMock = mock(async (input: string | URL | Request, init?: RequestInit) => {
107
- const url = String(input);
108
- requests.push({
109
- method: init?.method ?? "GET",
110
- url,
111
- body: init?.body ? JSON.parse(String(init.body)) : undefined,
112
- });
113
-
114
- if ((init?.method ?? "GET") === "GET") {
115
- return new Response(
116
- JSON.stringify({
117
- data: {
118
- data: {
119
- existing_field: "keep-me",
120
- },
121
- },
122
- }),
123
- { status: 200 }
124
- );
125
- }
126
-
127
- return new Response(JSON.stringify({}), { status: 200 });
128
- });
129
-
130
- globalThis.fetch = fetchMock as unknown as typeof fetch;
131
-
132
- await upsertVaultSecretFields({
133
- path: "prod/providers/clerk",
134
- fields: {
135
- publishable_key: "pk_live_example",
136
- secret_key: "sk_live_example",
137
- webhook_secret: "whsec_example",
138
- },
139
- });
140
-
141
- expect(requests).toEqual([
142
- {
143
- method: "GET",
144
- url: "https://vault.example.com/v1/secret/data/prod/providers/clerk",
145
- },
146
- {
147
- method: "POST",
148
- url: "https://vault.example.com/v1/secret/data/prod/providers/clerk",
149
- body: {
150
- data: {
151
- existing_field: "keep-me",
152
- publishable_key: "pk_live_example",
153
- secret_key: "sk_live_example",
154
- webhook_secret: "whsec_example",
155
- },
156
- },
157
- },
158
- ]);
159
- });
package/src/vault.ts CHANGED
@@ -13,14 +13,6 @@ type VaultSecretOptions = {
13
13
  field?: string;
14
14
  };
15
15
 
16
- type VaultWriteOptions = {
17
- addr?: string;
18
- token?: string;
19
- mount?: string;
20
- path: string;
21
- fields: Record<string, string>;
22
- };
23
-
24
16
  export async function resolveNeonApiKey() {
25
17
  const direct = process.env.NEON_API_KEY?.trim();
26
18
  if (direct) {
@@ -34,59 +26,24 @@ export async function resolveNeonApiKey() {
34
26
  }
35
27
 
36
28
  export async function readVaultSecret(options: VaultSecretOptions = {}) {
37
- const field = options.field?.trim() ?? "value";
38
- const payload = await readVaultSecretData(options);
29
+ const addr = options.addr ?? process.env.VAULT_ADDR?.trim() ?? "";
30
+ const token = options.token ?? (await resolveVaultToken());
39
31
  const mount = options.mount ?? process.env.VAULT_SECRET_MOUNT?.trim() ?? DEFAULT_VAULT_SECRET_MOUNT;
40
32
  const path = options.path?.trim() ?? "";
41
- const normalizedMount = mount.replace(/^\/+|\/+$/g, "");
42
- const normalizedPath = path.replace(/^\/+/g, "");
43
- const value = payload[field]?.trim();
44
- if (!value) {
45
- throw new Error(`Vault secret field ${field} is empty at ${normalizedMount}/${normalizedPath}`);
46
- }
47
-
48
- return value;
49
- }
50
-
51
- export async function readVaultSecretFields(options: VaultSecretOptions = {}) {
52
- return readVaultSecretData(options);
53
- }
33
+ const field = options.field?.trim() ?? "value";
54
34
 
55
- export async function upsertVaultSecretFields(options: VaultWriteOptions) {
56
- const connection = await resolveVaultConnection(options);
57
- const url = vaultKv2Url(connection);
35
+ if (!addr || !token || !path) {
36
+ throw new Error("Vault secret resolution requires VAULT_ADDR, a Vault token, and a secret path");
37
+ }
58
38
 
59
- const existing = await readVaultSecretData({ ...options, path: connection.normalizedPath }).catch((error) => {
60
- if (error instanceof Error && error.message.startsWith("Vault read failed: 404")) {
61
- return {};
62
- }
63
- throw error;
64
- });
39
+ const normalizedAddr = addr.replace(/\/+$/g, "");
40
+ const normalizedMount = mount.replace(/^\/+|\/+$/g, "");
41
+ const normalizedPath = path.replace(/^\/+/g, "");
42
+ const url = `${normalizedAddr}/v1/${normalizedMount}/data/${normalizedPath}`;
65
43
 
66
44
  const response = await fetch(url, {
67
- method: "POST",
68
45
  headers: {
69
- "Content-Type": "application/json",
70
- "X-Vault-Token": connection.token,
71
- },
72
- body: JSON.stringify({
73
- data: {
74
- ...existing,
75
- ...trimFields(options.fields),
76
- },
77
- }),
78
- });
79
-
80
- if (!response.ok) {
81
- throw new Error(`Vault write failed: ${response.status} ${response.statusText}`);
82
- }
83
- }
84
-
85
- async function readVaultSecretData(options: VaultSecretOptions = {}) {
86
- const connection = await resolveVaultConnection(options);
87
- const response = await fetch(vaultKv2Url(connection), {
88
- headers: {
89
- "X-Vault-Token": connection.token,
46
+ "X-Vault-Token": token,
90
47
  },
91
48
  });
92
49
 
@@ -100,31 +57,12 @@ async function readVaultSecretData(options: VaultSecretOptions = {}) {
100
57
  };
101
58
  };
102
59
 
103
- return payload.data?.data ?? {};
104
- }
105
-
106
- async function resolveVaultConnection(options: Omit<VaultWriteOptions, "fields"> | VaultSecretOptions) {
107
- const addr = options.addr ?? process.env.VAULT_ADDR?.trim() ?? "";
108
- const token = options.token ?? (await resolveVaultToken());
109
- const mount = options.mount ?? process.env.VAULT_SECRET_MOUNT?.trim() ?? DEFAULT_VAULT_SECRET_MOUNT;
110
- const path = options.path?.trim() ?? "";
111
-
112
- if (!addr || !token || !path) {
113
- throw new Error("Vault secret resolution requires VAULT_ADDR, a Vault token, and a secret path");
60
+ const value = payload.data?.data?.[field]?.trim();
61
+ if (!value) {
62
+ throw new Error(`Vault secret field ${field} is empty at ${normalizedMount}/${normalizedPath}`);
114
63
  }
115
64
 
116
- const normalizedAddr = addr.replace(/\/+$/g, "");
117
- const normalizedMount = mount.replace(/^\/+|\/+$/g, "");
118
- const normalizedPath = path.replace(/^\/+/g, "");
119
- return { normalizedAddr, normalizedMount, normalizedPath, token };
120
- }
121
-
122
- function vaultKv2Url(connection: Awaited<ReturnType<typeof resolveVaultConnection>>) {
123
- return `${connection.normalizedAddr}/v1/${connection.normalizedMount}/data/${connection.normalizedPath}`;
124
- }
125
-
126
- function trimFields(fields: Record<string, string>) {
127
- return Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, value.trim()]));
65
+ return value;
128
66
  }
129
67
 
130
68
  async function resolveVaultToken() {
@@ -1,13 +1,13 @@
1
1
  # {{SERVICE_NAME}}
2
2
 
3
- Generated by `create-service`.
3
+ Generated by `service create`.
4
4
 
5
5
  This `{{PROFILE}}` profile targets `{{RUNTIME}} + {{FRAMEWORK}}` on Cloud Run with:
6
6
 
7
7
  - one generated `service.yaml` manifest
8
8
  - a lightweight `{{EXAMPLE_LABEL}}` example surface
9
9
  - local Docker Compose Postgres for first-run development
10
- - a local `service` CLI for create, deploy, doctor, dashboards, and destroy
10
+ - the `service` CLI for create, deploy, doctor, dashboards, and destroy
11
11
  - GCP project create with billing and quota-project-aware `gcloud` calls
12
12
  - Neon-backed remote database provisioning during create and deploy
13
13
  - Better Auth client-credentials resource-server registration through `authctl`
@@ -66,7 +66,7 @@ Create, deploy, and destroy use:
66
66
  - known-good CLIs first, especially `gcloud`
67
67
  - `gcloud`
68
68
  - `NEON_API_KEY`, or a working Vault login via `VAULT_ADDR` plus `VAULT_TOKEN`, `VAULT_TOKEN_FILE`, or `~/.vault-token`
69
- - the package-local CLI via `npx --no-install service ...`
69
+ - the repo-aware `service` CLI from this package
70
70
 
71
71
  Local provisioning intentionally prefers known-good CLIs over SDKs for Google Cloud operations.
72
72
 
@@ -199,10 +199,10 @@ secrets only when you add a provider adapter. A generic adapter can honor:
199
199
 
200
200
  The one-command production create path is designed for a fresh standalone service.
201
201
 
202
- When generated through `create-service`, the intended flow is:
202
+ The intended one-command flow is:
203
203
 
204
204
  ```bash
205
- bun create service {{SERVICE_NAME}} --yes
205
+ service create {{SERVICE_NAME}} --yes
206
206
  ```
207
207
 
208
208
  That command scaffolds this package, runs `service create`, deploys the production
@@ -26,6 +26,11 @@ import {
26
26
  async function main(argv = Bun.argv.slice(2)) {
27
27
  const [command, ...rest] = argv;
28
28
 
29
+ if (!command || command === "--help" || command === "-h" || command === "help") {
30
+ console.log("Usage: service <create|deploy|migrate|seed|dashboards|dns|doctor|destroy|auth|sdk> [args]");
31
+ return;
32
+ }
33
+
29
34
  if (command === "create") {
30
35
  await runMain("Create", async () => {
31
36
  assertServiceNameAvailable(config.serviceName);
@@ -1,12 +1,12 @@
1
1
  # {{SERVICE_NAME}}
2
2
 
3
- Generated by `create-service`.
3
+ Generated by `service create`.
4
4
 
5
5
  This `{{PROFILE}}` profile targets `{{RUNTIME}} + {{FRAMEWORK}}` on Cloudflare Workers with:
6
6
 
7
7
  - one `wrangler.toml`
8
8
  - a lightweight waitlist/launch API
9
- - a local `service` CLI for create, deploy, doctor, dashboards, DNS, and destroy
9
+ - the `service` CLI for create, deploy, doctor, dashboards, DNS, and destroy
10
10
  - Cron Trigger wiring for scheduled follow-up work
11
11
  - a Hyperdrive binding for Neon-backed Postgres persistence
12
12
  - a production API origin at `https://{{API_HOSTNAME}}`
@@ -18,8 +18,8 @@ wrangler dev
18
18
  bun run test
19
19
  bun run lint
20
20
  bun run migrate
21
- bun run create
22
- bun run deploy
21
+ service create
22
+ service deploy
23
23
  bun run dashboards
24
24
  bun run doctor
25
25
  bun run destroy
@@ -50,7 +50,7 @@ small waitlist/trigger schema on first use.
50
50
  ## Production
51
51
 
52
52
  ```bash
53
- bun run create
53
+ service create
54
54
  ```
55
55
 
56
56
  `service create` deploys the Worker through Wrangler. The custom domain is
@@ -60,7 +60,7 @@ configured in `wrangler.toml`:
60
60
  https://{{API_HOSTNAME}}
61
61
  ```
62
62
 
63
- Use `bun run doctor` after create to verify Wrangler auth, route config, Cron,
63
+ Use `service doctor` after create to verify Wrangler auth, route config, Cron,
64
64
  Hyperdrive, dashboard tooling, auth tooling, and deployed health.
65
65
 
66
66
  If the Hyperdrive binding id is empty, `service create` uses `DATABASE_URL`, or
@@ -17,6 +17,11 @@ type DoctorStatus = "pass" | "warn" | "fail";
17
17
  async function main(argv = Bun.argv.slice(2)) {
18
18
  const [command, ...rest] = argv;
19
19
 
20
+ if (!command || command === "--help" || command === "-h" || command === "help") {
21
+ console.log("Usage: service <create|deploy|migrate|seed|dashboards|dns|doctor|destroy|auth|sdk> [args]");
22
+ return;
23
+ }
24
+
20
25
  if (command === "create") {
21
26
  return runMain("Create", async () => {
22
27
  ensureAuthResourceServer();
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env bun
2
- import "../index.ts";
File without changes