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 +1 -1
- package/src/git-bootstrap.test.ts +49 -1
- package/src/git-bootstrap.ts +13 -1
- package/src/scaffold.test.ts +9 -0
- package/src/scaffold.ts +7 -3
- package/templates/shared/.env.example +40 -0
- package/templates/shared/.github/workflows/deploy.yml +2 -0
- package/templates/shared/.github/workflows/preview.yml +1 -0
- package/templates/shared/_gitignore +13 -0
- package/templates/targets/workers/.github/workflows/deploy.yml +1 -0
- package/templates/variants/go-chi/Dockerfile +0 -1
package/package.json
CHANGED
|
@@ -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 {
|
|
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
|
+
}
|
package/src/git-bootstrap.ts
CHANGED
|
@@ -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(
|
|
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
|
}
|
package/src/scaffold.test.ts
CHANGED
|
@@ -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: "
|
|
244
|
+
WORKFLOW_DEPLOY_MAIN_COMMAND: "service deploy --ci",
|
|
241
245
|
WORKFLOW_DEPLOY_PREVIEW_COMMAND:
|
|
242
|
-
"
|
|
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 }}
|