create-svc 0.1.54 → 0.1.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-svc",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
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",
@@ -2,7 +2,13 @@ import { expect, test } from "bun:test";
2
2
  import { mkdir, mkdtemp, realpath, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
- import { buildGitBootstrapConfig, findExistingGitWorktree, manualGitHubDeleteCommand, markGitHubRepositoryDeleteOnDestroy } from "./git-bootstrap";
5
+ import {
6
+ buildGitBootstrapConfig,
7
+ commitAndPushGeneratedArtifacts,
8
+ findExistingGitWorktree,
9
+ manualGitHubDeleteCommand,
10
+ markGitHubRepositoryDeleteOnDestroy,
11
+ } from "./git-bootstrap";
6
12
 
7
13
  test("buildGitBootstrapConfig defaults to anmho private repo creation", () => {
8
14
  expect(buildGitBootstrapConfig("launch-api", undefined)).toEqual({
@@ -41,6 +47,36 @@ test("markGitHubRepositoryDeleteOnDestroy records generated repo ownership", asy
41
47
  expect(await Bun.file(join(root, "service.jsonc")).text()).toContain('"delete_on_destroy": true');
42
48
  });
43
49
 
50
+ test("commitAndPushGeneratedArtifacts excludes local dependencies and dev runtime files", async () => {
51
+ const root = await mkdtemp(join(tmpdir(), "create-svc-git-"));
52
+ const remote = await mkdtemp(join(tmpdir(), "create-svc-remote-"));
53
+ run(["git", "init", "--bare"], remote);
54
+ run(["git", "init", "-b", "main"], root);
55
+ run(["git", "config", "user.name", "create-svc test"], root);
56
+ run(["git", "config", "user.email", "create-svc@example.invalid"], root);
57
+ run(["git", "remote", "add", "origin", remote], root);
58
+ await writeFile(join(root, "README.md"), "# generated\n");
59
+ run(["git", "add", "."], root);
60
+ run(["git", "commit", "-m", "Initial commit"], root);
61
+ run(["git", "push", "-u", "origin", "main"], root);
62
+
63
+ await mkdir(join(root, "node_modules", "large-package"), { recursive: true });
64
+ await mkdir(join(root, ".service"), { recursive: true });
65
+ await mkdir(join(root, ".wrangler", "tmp"), { recursive: true });
66
+ await writeFile(join(root, "node_modules", "large-package", "artifact.bin"), "large");
67
+ await writeFile(join(root, ".service", "local-dev.log"), "log");
68
+ await writeFile(join(root, ".service", "local-dev.pid"), "123");
69
+ await writeFile(join(root, ".wrangler", "tmp", "bundle.js"), "bundle");
70
+ await writeFile(join(root, "service.jsonc"), '{ "deployed": true }\n');
71
+
72
+ expect(commitAndPushGeneratedArtifacts(root, "Record generated deployment artifacts")).toEqual({ committed: true });
73
+
74
+ expect(git(["ls-files"], root)).toContain("service.jsonc");
75
+ expect(git(["ls-files"], root)).not.toContain("node_modules");
76
+ expect(git(["ls-files"], root)).not.toContain(".service/local-dev.log");
77
+ expect(git(["ls-files"], root)).not.toContain(".wrangler");
78
+ });
79
+
44
80
  function run(command: string[], cwd: string) {
45
81
  const result = Bun.spawnSync(command, {
46
82
  cwd,
@@ -51,3 +87,15 @@ function run(command: string[], cwd: string) {
51
87
  throw new Error(result.stderr.toString());
52
88
  }
53
89
  }
90
+
91
+ function git(command: string[], cwd: string) {
92
+ const result = Bun.spawnSync(["git", ...command], {
93
+ cwd,
94
+ stdout: "pipe",
95
+ stderr: "pipe",
96
+ });
97
+ if (result.exitCode !== 0) {
98
+ throw new Error(result.stderr.toString());
99
+ }
100
+ return result.stdout.toString();
101
+ }
@@ -58,7 +58,19 @@ export async function bootstrapGitHubRepository(targetDir: string, config: GitBo
58
58
  }
59
59
 
60
60
  export function commitAndPushGeneratedArtifacts(targetDir: string, message: string) {
61
- run(["git", "add", "."], targetDir);
61
+ run(
62
+ [
63
+ "git",
64
+ "add",
65
+ "--all",
66
+ ".",
67
+ ":!node_modules",
68
+ ":!.service/*.log",
69
+ ":!.service/*.pid",
70
+ ":!.wrangler",
71
+ ],
72
+ targetDir
73
+ );
62
74
  if (!hasStagedChanges(targetDir)) {
63
75
  return { committed: false };
64
76
  }
@@ -105,6 +105,8 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
105
105
 
106
106
  const gitignore = await Bun.file(join(generatedRoot, ".gitignore")).text();
107
107
  expect(gitignore).toContain("node_modules");
108
+ expect(gitignore).toContain(".service/*.log");
109
+ expect(gitignore).toContain(".wrangler");
108
110
  expect(await Bun.file(join(generatedRoot, "website", "package.json")).exists()).toBeFalse();
109
111
 
110
112
  const dockerCompose = await Bun.file(join(generatedRoot, "docker-compose.yml")).text();
@@ -147,6 +149,8 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
147
149
  const deployWorkflow = await Bun.file(join(generatedRoot, ".github", "workflows", "deploy.yml")).text();
148
150
  expect(deployWorkflow).toContain("branches:");
149
151
  expect(deployWorkflow).toContain("- main");
152
+ expect(deployWorkflow).toContain("gcloud components install beta --quiet");
153
+ expect(deployWorkflow).toContain("bun install -g create-svc@latest");
150
154
  expect(deployWorkflow).toContain("service deploy --ci");
151
155
  expect(deployWorkflow).toContain("bun run dashboards");
152
156
  expect(deployWorkflow).toContain("GCX_ENABLED");
@@ -160,6 +164,11 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
160
164
  expect(goSumExists).toBeTrue();
161
165
  const dockerfile = await Bun.file(join(generatedRoot, "Dockerfile")).text();
162
166
  expect(dockerfile).toContain("COPY go.mod go.sum ./");
167
+ if (variant.framework === "chi") {
168
+ expect(dockerfile).not.toContain("COPY gen ./gen");
169
+ } else {
170
+ expect(dockerfile).toContain("COPY gen ./gen");
171
+ }
163
172
  expect(packageJson).toContain('"dev": "make dev"');
164
173
  expect(packageJson).toContain('"service": "service"');
165
174
  expect(packageJson).toContain('"migrate": "service migrate"');
package/src/scaffold.ts CHANGED
@@ -69,7 +69,7 @@ export async function scaffoldProject(config: ScaffoldConfig) {
69
69
  continue;
70
70
  }
71
71
  const sourcePath = join(template.root, relativePath);
72
- const destinationPath = join(targetDir, relativePath);
72
+ const destinationPath = join(targetDir, templateDestinationPath(relativePath));
73
73
  const raw = await Bun.file(sourcePath).text();
74
74
  const rendered = renderTemplate(raw, replacements);
75
75
 
@@ -81,6 +81,10 @@ export async function scaffoldProject(config: ScaffoldConfig) {
81
81
  await writeLocalEnvFile(targetDir, replacements);
82
82
  }
83
83
 
84
+ function templateDestinationPath(relativePath: string) {
85
+ return relativePath === "_gitignore" ? ".gitignore" : relativePath;
86
+ }
87
+
84
88
  function shouldSkipForTarget(target: DeployTarget, templateKind: "shared" | "variant" | "target", relativePath: string) {
85
89
  if (
86
90
  relativePath === "scripts/authctl.ts" ||
@@ -237,9 +241,9 @@ function buildReplacements(config: ScaffoldConfig) {
237
241
  COMMAND_DEPLOY: "service deploy",
238
242
  COMMAND_OBSERVABILITY_BOOTSTRAP:
239
243
  config.runtime === "bun" ? "bun run observability-bootstrap" : "make observability-bootstrap",
240
- WORKFLOW_DEPLOY_MAIN_COMMAND: "npm install -g create-svc@latest && service deploy --ci",
244
+ WORKFLOW_DEPLOY_MAIN_COMMAND: "service deploy --ci",
241
245
  WORKFLOW_DEPLOY_PREVIEW_COMMAND:
242
- "npm install -g create-svc@latest && service deploy --ci --environment preview --name ${{ github.ref_name }}",
246
+ "service deploy --ci --environment preview --name ${{ github.ref_name }}",
243
247
  WORKFLOW_DEPLOY_MAIN_DOC_COMMAND: "service deploy --ci",
244
248
  WORKFLOW_DEPLOY_PREVIEW_DOC_COMMAND: "service deploy --ci --environment preview --name <branch-name>",
245
249
  COMMAND_AUTH_RESOURCE: "service auth resource-server",
@@ -0,0 +1,40 @@
1
+ # Local development defaults.
2
+ # The scaffold also writes a ready-to-use .env.local for Docker Compose Postgres.
3
+
4
+ DATABASE_URL=postgres://{{LOCAL_DATABASE_USER}}:{{LOCAL_DATABASE_PASSWORD}}@127.0.0.1:{{LOCAL_DATABASE_PORT}}/{{LOCAL_DATABASE_NAME}}?sslmode=disable
5
+
6
+ TEMPORAL_ENABLED=false
7
+ TEMPORAL_ADDRESS=localhost:7233
8
+ TEMPORAL_NAMESPACE=default
9
+ TEMPORAL_TASK_QUEUE={{SERVICE_ID}}
10
+ # Optional for Temporal Cloud. `service create` writes this to Secret Manager.
11
+ TEMPORAL_API_KEY=
12
+
13
+ AUTH_ENABLED=false
14
+ AUTH_ISSUER=https://auth.anmho.com
15
+ AUTH_AUDIENCE=api://{{SERVICE_ID}}
16
+ AUTH_JWKS_URL=https://auth.anmho.com/api/auth/jwks
17
+
18
+ # Production authctl operations such as `service create` and
19
+ # `service auth client create` load Cloudflare Access values from Vault by
20
+ # default. Direct env values still override Vault when set.
21
+ AUTH_INTERNAL_BASE_URL=https://auth.anmho.com/internal
22
+ CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID=
23
+ CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET=
24
+
25
+ # Remote bootstrap and deploy can use Neon admin credentials directly or through Vault.
26
+ # Do not commit VAULT_TOKEN. Prefer `vault login`; the CLI will also use
27
+ # VAULT_TOKEN_FILE or ~/.vault-token when available.
28
+
29
+ VAULT_ADDR=https://vault.example.com
30
+ VAULT_SECRET_MOUNT=secret
31
+ VAULT_AUTHCTL_ACCESS_PATH=prod/apps/auth/authctl/cloudflare-access
32
+ VAULT_AUTHCTL_ACCESS_BASE_URL_FIELD=AUTH_INTERNAL_BASE_URL
33
+ VAULT_AUTHCTL_ACCESS_CLIENT_ID_FIELD=CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_ID
34
+ VAULT_AUTHCTL_ACCESS_CLIENT_SECRET_FIELD=CLOUDFLARE_ACCESS_SERVICE_TOKEN_CLIENT_SECRET
35
+ VAULT_NEON_API_KEY_PATH=prod/providers/neon
36
+ VAULT_NEON_API_KEY_FIELD=api_key
37
+
38
+ # Optional provider credentials can be read from Vault or environment by
39
+ # generated adapters you add later. The base waitlist service does not require
40
+ # provider secrets.
@@ -24,7 +24,9 @@ jobs:
24
24
  workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
25
25
  service_account: ${{ vars.GCP_DEPLOYER_SERVICE_ACCOUNT }}
26
26
  - uses: google-github-actions/setup-gcloud@v2
27
+ - run: gcloud components install beta --quiet
27
28
  - run: bun install
29
+ - run: bun install -g create-svc@latest
28
30
  - run: {{WORKFLOW_DEPLOY_MAIN_COMMAND}}
29
31
  env:
30
32
  NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
@@ -25,6 +25,7 @@ jobs:
25
25
  service_account: ${{ vars.GCP_DEPLOYER_SERVICE_ACCOUNT }}
26
26
  - uses: google-github-actions/setup-gcloud@v2
27
27
  - run: bun install
28
+ - run: bun install -g create-svc@latest
28
29
  - run: {{WORKFLOW_DEPLOY_PREVIEW_COMMAND}}
29
30
  env:
30
31
  NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
@@ -0,0 +1,13 @@
1
+ node_modules
2
+ .env
3
+ .env.local
4
+ .env.*
5
+ coverage
6
+ dist
7
+ out
8
+ .cloudrun.rendered.yaml
9
+ .service/*.log
10
+ .service/*.pid
11
+ .wrangler
12
+ .DS_Store
13
+ {{GITIGNORE_EXTRA}}
@@ -12,6 +12,7 @@ jobs:
12
12
  - uses: actions/checkout@v4
13
13
  - uses: oven-sh/setup-bun@v2
14
14
  - run: bun install
15
+ - run: bun install -g create-svc@latest
15
16
  - run: bun run deploy
16
17
  env:
17
18
  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
@@ -3,7 +3,6 @@ FROM golang:1.25.4 AS builder
3
3
  WORKDIR /app
4
4
 
5
5
  COPY go.mod go.sum ./
6
- COPY gen ./gen
7
6
  COPY internal ./internal
8
7
  COPY cmd ./cmd
9
8