create-svc 0.1.54 → 0.1.55

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.55",
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,7 @@ 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("bun install -g create-svc@latest");
150
153
  expect(deployWorkflow).toContain("service deploy --ci");
151
154
  expect(deployWorkflow).toContain("bun run dashboards");
152
155
  expect(deployWorkflow).toContain("GCX_ENABLED");
@@ -160,6 +163,11 @@ test("scaffolds all runtime/framework variants with shared cloudrun config", asy
160
163
  expect(goSumExists).toBeTrue();
161
164
  const dockerfile = await Bun.file(join(generatedRoot, "Dockerfile")).text();
162
165
  expect(dockerfile).toContain("COPY go.mod go.sum ./");
166
+ if (variant.framework === "chi") {
167
+ expect(dockerfile).not.toContain("COPY gen ./gen");
168
+ } else {
169
+ expect(dockerfile).toContain("COPY gen ./gen");
170
+ }
163
171
  expect(packageJson).toContain('"dev": "make dev"');
164
172
  expect(packageJson).toContain('"service": "service"');
165
173
  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",
@@ -605,7 +605,6 @@ export function ensureProductionDomainMapping(serviceName: string) {
605
605
  }
606
606
 
607
607
  const result = gcloud([
608
- "beta",
609
608
  "run",
610
609
  "domain-mappings",
611
610
  "create",
@@ -627,7 +626,6 @@ export function describeProductionDomainMapping():
627
626
  | undefined {
628
627
  const result = gcloud(
629
628
  [
630
- "beta",
631
629
  "run",
632
630
  "domain-mappings",
633
631
  "describe",
@@ -680,7 +678,6 @@ export function deleteProductionDomainMapping() {
680
678
  deleteCloudflareDnsRecord();
681
679
  gcloud(
682
680
  [
683
- "beta",
684
681
  "run",
685
682
  "domain-mappings",
686
683
  "delete",
@@ -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.
@@ -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_MAIN_COMMAND}}
29
30
  env:
30
31
  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