create-svc 0.1.86 → 0.1.87
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/scaffold.ts +2 -0
- package/templates/shared/README.md +17 -0
- package/templates/shared/scripts/e2e.ts +275 -0
- package/templates/variants/bun-connectrpc/Makefile +10 -1
- package/templates/variants/bun-connectrpc/package.json +3 -0
- package/templates/variants/bun-hono/Makefile +10 -1
- package/templates/variants/bun-hono/package.json +3 -0
- package/templates/variants/go-chi/Makefile +10 -1
- package/templates/variants/go-chi/package.json +3 -0
- package/templates/variants/go-connectrpc/Makefile +10 -1
- package/templates/variants/go-connectrpc/package.json +3 -0
package/package.json
CHANGED
package/src/scaffold.ts
CHANGED
|
@@ -244,6 +244,8 @@ function buildReplacements(config: ScaffoldConfig) {
|
|
|
244
244
|
COMMAND_GEN: config.runtime === "bun" ? "bun run gen" : "make gen",
|
|
245
245
|
COMMAND_LINT: config.runtime === "bun" ? "bun run lint" : "make lint",
|
|
246
246
|
COMMAND_TEST: config.runtime === "bun" ? "bun run test" : "make test",
|
|
247
|
+
COMMAND_TEST_E2E_LOCAL: config.runtime === "bun" ? "bun run test:e2e:local" : "make test-e2e-local",
|
|
248
|
+
COMMAND_TEST_E2E_PROD: config.runtime === "bun" ? "bun run test:e2e:prod" : "make test-e2e-prod",
|
|
247
249
|
COMMAND_DEV_DOWN: "service dev down",
|
|
248
250
|
COMMAND_BOOTSTRAP: "service create",
|
|
249
251
|
COMMAND_DEPLOY: "service deploy",
|
|
@@ -29,6 +29,8 @@ console to create and deploy.
|
|
|
29
29
|
{{COMMAND_GEN}}
|
|
30
30
|
{{COMMAND_LINT}}
|
|
31
31
|
{{COMMAND_TEST}}
|
|
32
|
+
{{COMMAND_TEST_E2E_LOCAL}}
|
|
33
|
+
{{COMMAND_TEST_E2E_PROD}}
|
|
32
34
|
{{COMMAND_BOOTSTRAP}}
|
|
33
35
|
{{COMMAND_DEPLOY}}
|
|
34
36
|
{{COMMAND_PROTECT_MAIN}}
|
|
@@ -60,6 +62,21 @@ Local runtime uses:
|
|
|
60
62
|
|
|
61
63
|
No cloud credentials are required for local HTTP development after Docker and Postgres are running.
|
|
62
64
|
|
|
65
|
+
Run the local end-to-end test against the already-running local service:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
{{COMMAND_TEST_E2E_LOCAL}}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The production end-to-end test exercises health and public webhook idempotency,
|
|
72
|
+
then requires Cloud Logging rows and Cloud Monitoring
|
|
73
|
+
`run.googleapis.com/container/instance_count` rows for the current Cloud Run API
|
|
74
|
+
and worker revisions:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
{{COMMAND_TEST_E2E_PROD}}
|
|
78
|
+
```
|
|
79
|
+
|
|
63
80
|
## Temporal
|
|
64
81
|
|
|
65
82
|
Temporal is enabled by default for generated services.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
type Target = "local" | "prod";
|
|
2
|
+
|
|
3
|
+
export {};
|
|
4
|
+
|
|
5
|
+
type CommandOptions = {
|
|
6
|
+
allowFailure?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type CloudRunService = {
|
|
10
|
+
status?: {
|
|
11
|
+
latestReadyRevisionName?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type MonitoringSeries = {
|
|
16
|
+
resource?: {
|
|
17
|
+
labels?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
points?: Array<{
|
|
20
|
+
value?: {
|
|
21
|
+
int64Value?: string;
|
|
22
|
+
doubleValue?: number;
|
|
23
|
+
};
|
|
24
|
+
interval?: {
|
|
25
|
+
endTime?: string;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const args = parseArgs(Bun.argv.slice(2));
|
|
31
|
+
const serviceName = "{{SERVICE_NAME}}";
|
|
32
|
+
const projectId = "{{PROJECT_ID}}";
|
|
33
|
+
const region = "{{REGION}}";
|
|
34
|
+
const apiHostname = "{{API_HOSTNAME}}";
|
|
35
|
+
const baseUrl = args.url ?? (args.target === "prod" ? `https://${apiHostname}` : `http://127.0.0.1:${Bun.env.PORT || "3000"}`);
|
|
36
|
+
const proofId = `e2e-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
37
|
+
|
|
38
|
+
section(`${serviceName} ${args.target} e2e`);
|
|
39
|
+
detail("base_url", baseUrl);
|
|
40
|
+
detail("event_id", proofId);
|
|
41
|
+
|
|
42
|
+
await requestJSON(`${baseUrl}/healthz`, { expectStatus: 200 });
|
|
43
|
+
|
|
44
|
+
const webhookPayload = {
|
|
45
|
+
id: proofId,
|
|
46
|
+
source: "generated-e2e",
|
|
47
|
+
service: serviceName,
|
|
48
|
+
target: args.target,
|
|
49
|
+
};
|
|
50
|
+
const firstWebhook = await requestJSON(`${baseUrl}/webhooks/generated-e2e`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
"x-webhook-event-id": proofId,
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify(webhookPayload),
|
|
57
|
+
expectStatus: 202,
|
|
58
|
+
});
|
|
59
|
+
detail("webhook_first", JSON.stringify(firstWebhook));
|
|
60
|
+
|
|
61
|
+
const secondWebhook = await requestJSON(`${baseUrl}/webhooks/generated-e2e`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: {
|
|
64
|
+
"content-type": "application/json",
|
|
65
|
+
"x-webhook-event-id": proofId,
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(webhookPayload),
|
|
68
|
+
expectStatus: 200,
|
|
69
|
+
});
|
|
70
|
+
detail("webhook_duplicate", JSON.stringify(secondWebhook));
|
|
71
|
+
if (!isDuplicateWebhook(secondWebhook)) {
|
|
72
|
+
throw new Error("second webhook delivery did not report duplicate=true");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (args.target === "prod") {
|
|
76
|
+
await Bun.sleep(5_000);
|
|
77
|
+
const state = await printCloudRunState();
|
|
78
|
+
await printCloudLogs(serviceName, state.apiRevision);
|
|
79
|
+
await printCloudLogs(`${serviceName}-worker`, state.workerRevision);
|
|
80
|
+
await printCloudMetrics(state);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
section("e2e complete");
|
|
84
|
+
|
|
85
|
+
async function requestJSON(
|
|
86
|
+
url: string,
|
|
87
|
+
options: RequestInit & { expectStatus: number } = { expectStatus: 200 }
|
|
88
|
+
) {
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
...options,
|
|
91
|
+
signal: AbortSignal.timeout(15_000),
|
|
92
|
+
});
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
if (response.status !== options.expectStatus) {
|
|
95
|
+
throw new Error(`${url} returned ${response.status}, expected ${options.expectStatus}: ${text}`);
|
|
96
|
+
}
|
|
97
|
+
if (!text) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(text);
|
|
102
|
+
} catch {
|
|
103
|
+
return { text };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isDuplicateWebhook(value: unknown) {
|
|
108
|
+
return Boolean(value && typeof value === "object" && "duplicate" in value && value.duplicate === true);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function printCloudRunState() {
|
|
112
|
+
const [api, worker] = await Promise.all([
|
|
113
|
+
describeCloudRunService(serviceName),
|
|
114
|
+
describeCloudRunService(`${serviceName}-worker`),
|
|
115
|
+
]);
|
|
116
|
+
const apiRevision = api.status?.latestReadyRevisionName ?? "";
|
|
117
|
+
const workerRevision = worker.status?.latestReadyRevisionName ?? "";
|
|
118
|
+
if (!apiRevision) {
|
|
119
|
+
throw new Error(`Cloud Run service ${serviceName} did not report latestReadyRevisionName`);
|
|
120
|
+
}
|
|
121
|
+
if (!workerRevision) {
|
|
122
|
+
throw new Error(`Cloud Run service ${serviceName}-worker did not report latestReadyRevisionName`);
|
|
123
|
+
}
|
|
124
|
+
detail("api_revision", apiRevision);
|
|
125
|
+
detail("worker_revision", workerRevision);
|
|
126
|
+
return { apiRevision, workerRevision };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function describeCloudRunService(name: string): Promise<CloudRunService> {
|
|
130
|
+
const output = await command([
|
|
131
|
+
"gcloud",
|
|
132
|
+
"run",
|
|
133
|
+
"services",
|
|
134
|
+
"describe",
|
|
135
|
+
name,
|
|
136
|
+
"--project",
|
|
137
|
+
projectId,
|
|
138
|
+
"--region",
|
|
139
|
+
region,
|
|
140
|
+
"--format=json",
|
|
141
|
+
]);
|
|
142
|
+
return JSON.parse(output || "{}") as CloudRunService;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function printCloudLogs(name: string, revision: string) {
|
|
146
|
+
const filter = [
|
|
147
|
+
'resource.type="cloud_run_revision"',
|
|
148
|
+
`resource.labels.service_name="${name}"`,
|
|
149
|
+
`resource.labels.revision_name="${revision}"`,
|
|
150
|
+
].join(" AND ");
|
|
151
|
+
const output = await command([
|
|
152
|
+
"gcloud",
|
|
153
|
+
"logging",
|
|
154
|
+
"read",
|
|
155
|
+
filter,
|
|
156
|
+
"--project",
|
|
157
|
+
projectId,
|
|
158
|
+
"--limit=3",
|
|
159
|
+
"--format=json",
|
|
160
|
+
]);
|
|
161
|
+
const rows = JSON.parse(output || "[]") as unknown[];
|
|
162
|
+
section(`cloud logs ${name}`);
|
|
163
|
+
if (rows.length === 0) {
|
|
164
|
+
throw new Error(`Cloud Logging did not return rows for ${name} revision ${revision}`);
|
|
165
|
+
}
|
|
166
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function printCloudMetrics(expected: { apiRevision: string; workerRevision: string }) {
|
|
170
|
+
const expectedRevisions = new Set([expected.apiRevision, expected.workerRevision]);
|
|
171
|
+
for (let attempt = 1; attempt <= 6; attempt += 1) {
|
|
172
|
+
const rows = await readCloudMetrics();
|
|
173
|
+
const seenRevisions = new Set(rows.map((row) => row.revision).filter(Boolean));
|
|
174
|
+
section(`cloud metrics container/instance_count attempt ${attempt}`);
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
console.log(`${row.service}\t${row.revision}\t${row.value}\t${row.endTime}`);
|
|
177
|
+
}
|
|
178
|
+
const missing = [...expectedRevisions].filter((revision) => !seenRevisions.has(revision));
|
|
179
|
+
if (missing.length === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (attempt === 6) {
|
|
183
|
+
throw new Error(`Cloud Monitoring did not return current revision metrics: ${missing.join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
detail("metrics_waiting_for_revisions", missing.join(", "));
|
|
186
|
+
await Bun.sleep(20_000);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function readCloudMetrics() {
|
|
191
|
+
const end = new Date().toISOString();
|
|
192
|
+
const start = new Date(Date.now() - 20 * 60_000).toISOString();
|
|
193
|
+
const accessToken = await command(["gcloud", "auth", "print-access-token"]);
|
|
194
|
+
const filter = `metric.type="run.googleapis.com/container/instance_count" AND resource.labels.service_name=starts_with("${serviceName}")`;
|
|
195
|
+
const url = new URL(`https://monitoring.googleapis.com/v3/projects/${projectId}/timeSeries`);
|
|
196
|
+
url.searchParams.set("filter", filter);
|
|
197
|
+
url.searchParams.set("interval.startTime", start);
|
|
198
|
+
url.searchParams.set("interval.endTime", end);
|
|
199
|
+
url.searchParams.set("aggregation.alignmentPeriod", "60s");
|
|
200
|
+
url.searchParams.set("aggregation.perSeriesAligner", "ALIGN_SUM");
|
|
201
|
+
url.searchParams.set("view", "FULL");
|
|
202
|
+
const response = await fetch(url, {
|
|
203
|
+
headers: { authorization: `Bearer ${accessToken}` },
|
|
204
|
+
signal: AbortSignal.timeout(15_000),
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(`Cloud Monitoring query failed: ${response.status} ${await response.text()}`);
|
|
208
|
+
}
|
|
209
|
+
const data = (await response.json()) as { timeSeries?: MonitoringSeries[] };
|
|
210
|
+
return ((data.timeSeries ?? []) as MonitoringSeries[]).map((series) => {
|
|
211
|
+
const labels = series.resource?.labels ?? {};
|
|
212
|
+
const point = series.points?.[0];
|
|
213
|
+
const value = point?.value?.int64Value ?? point?.value?.doubleValue ?? "";
|
|
214
|
+
return {
|
|
215
|
+
service: labels.service_name ?? "",
|
|
216
|
+
revision: labels.revision_name ?? "",
|
|
217
|
+
value,
|
|
218
|
+
endTime: point?.interval?.endTime ?? "",
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function command(commandArgs: string[], options: CommandOptions = {}) {
|
|
224
|
+
const process = Bun.spawn(commandArgs, {
|
|
225
|
+
stdin: "ignore",
|
|
226
|
+
stdout: "pipe",
|
|
227
|
+
stderr: "pipe",
|
|
228
|
+
env: Bun.env,
|
|
229
|
+
});
|
|
230
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
231
|
+
new Response(process.stdout).text(),
|
|
232
|
+
new Response(process.stderr).text(),
|
|
233
|
+
process.exited,
|
|
234
|
+
]);
|
|
235
|
+
if (exitCode !== 0 && !options.allowFailure) {
|
|
236
|
+
throw new Error(`${commandArgs.join(" ")} failed with exit code ${exitCode}\n${stderr.trim()}`);
|
|
237
|
+
}
|
|
238
|
+
return stdout.trim();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function parseArgs(argv: string[]) {
|
|
242
|
+
let target: Target = "local";
|
|
243
|
+
let url = "";
|
|
244
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
245
|
+
const arg = argv[index];
|
|
246
|
+
if (arg === "--local") {
|
|
247
|
+
target = "local";
|
|
248
|
+
} else if (arg === "--prod") {
|
|
249
|
+
target = "prod";
|
|
250
|
+
} else if (arg === "--url") {
|
|
251
|
+
const value = argv[index + 1];
|
|
252
|
+
if (!value || value.startsWith("-")) {
|
|
253
|
+
throw new Error("Missing value for --url");
|
|
254
|
+
}
|
|
255
|
+
url = value;
|
|
256
|
+
index += 1;
|
|
257
|
+
} else if (arg.startsWith("--url=")) {
|
|
258
|
+
url = arg.slice("--url=".length);
|
|
259
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
260
|
+
console.log("Usage: bun run ./scripts/e2e.ts [--local|--prod] [--url <origin>]");
|
|
261
|
+
process.exit(0);
|
|
262
|
+
} else {
|
|
263
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { target, url };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function section(title: string) {
|
|
270
|
+
console.log(`\n== ${title} ==`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function detail(key: string, value: string) {
|
|
274
|
+
console.log(`${key}: ${value}`);
|
|
275
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: dev migrate gen lint test create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
1
|
+
.PHONY: dev migrate gen lint test test-e2e test-e2e-local test-e2e-prod create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
2
2
|
|
|
3
3
|
SERVICE := service
|
|
4
4
|
|
|
@@ -17,6 +17,15 @@ lint:
|
|
|
17
17
|
test:
|
|
18
18
|
bun test
|
|
19
19
|
|
|
20
|
+
test-e2e:
|
|
21
|
+
bun run ./scripts/e2e.ts $(ARGS)
|
|
22
|
+
|
|
23
|
+
test-e2e-local:
|
|
24
|
+
bun run ./scripts/e2e.ts --local $(ARGS)
|
|
25
|
+
|
|
26
|
+
test-e2e-prod:
|
|
27
|
+
bun run ./scripts/e2e.ts --prod $(ARGS)
|
|
28
|
+
|
|
20
29
|
create:
|
|
21
30
|
$(SERVICE) create
|
|
22
31
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"gen": "bun run ./scripts/codegen.ts",
|
|
10
10
|
"lint": "tsc --noEmit",
|
|
11
11
|
"test": "bun test",
|
|
12
|
+
"test:e2e": "bun run ./scripts/e2e.ts",
|
|
13
|
+
"test:e2e:local": "bun run ./scripts/e2e.ts --local",
|
|
14
|
+
"test:e2e:prod": "bun run ./scripts/e2e.ts --prod",
|
|
12
15
|
"create": "service create",
|
|
13
16
|
"deploy": "service deploy",
|
|
14
17
|
"protect-main": "service protect-main",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: dev migrate gen lint test create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
1
|
+
.PHONY: dev migrate gen lint test test-e2e test-e2e-local test-e2e-prod create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
2
2
|
|
|
3
3
|
SERVICE := service
|
|
4
4
|
|
|
@@ -17,6 +17,15 @@ lint:
|
|
|
17
17
|
test:
|
|
18
18
|
bun test
|
|
19
19
|
|
|
20
|
+
test-e2e:
|
|
21
|
+
bun run ./scripts/e2e.ts $(ARGS)
|
|
22
|
+
|
|
23
|
+
test-e2e-local:
|
|
24
|
+
bun run ./scripts/e2e.ts --local $(ARGS)
|
|
25
|
+
|
|
26
|
+
test-e2e-prod:
|
|
27
|
+
bun run ./scripts/e2e.ts --prod $(ARGS)
|
|
28
|
+
|
|
20
29
|
create:
|
|
21
30
|
$(SERVICE) create
|
|
22
31
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"gen": "bun run ./scripts/codegen.ts",
|
|
10
10
|
"lint": "tsc --noEmit",
|
|
11
11
|
"test": "bun test",
|
|
12
|
+
"test:e2e": "bun run ./scripts/e2e.ts",
|
|
13
|
+
"test:e2e:local": "bun run ./scripts/e2e.ts --local",
|
|
14
|
+
"test:e2e:prod": "bun run ./scripts/e2e.ts --prod",
|
|
12
15
|
"create": "service create",
|
|
13
16
|
"deploy": "service deploy",
|
|
14
17
|
"protect-main": "service protect-main",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: dev migrate migrate-lint gen lint test create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
1
|
+
.PHONY: dev migrate migrate-lint gen lint test test-e2e test-e2e-local test-e2e-prod create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
2
2
|
|
|
3
3
|
SERVICE := service
|
|
4
4
|
WITH_ENV := set -a; [ ! -f .env.local ] || . ./.env.local; set +a;
|
|
@@ -27,6 +27,15 @@ lint:
|
|
|
27
27
|
test:
|
|
28
28
|
bun test ./test
|
|
29
29
|
|
|
30
|
+
test-e2e:
|
|
31
|
+
bun run ./scripts/e2e.ts $(ARGS)
|
|
32
|
+
|
|
33
|
+
test-e2e-local:
|
|
34
|
+
bun run ./scripts/e2e.ts --local $(ARGS)
|
|
35
|
+
|
|
36
|
+
test-e2e-prod:
|
|
37
|
+
bun run ./scripts/e2e.ts --prod $(ARGS)
|
|
38
|
+
|
|
30
39
|
create:
|
|
31
40
|
$(SERVICE) create
|
|
32
41
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"gen": "make gen",
|
|
10
10
|
"lint": "make lint",
|
|
11
11
|
"test": "make test",
|
|
12
|
+
"test:e2e": "bun run ./scripts/e2e.ts",
|
|
13
|
+
"test:e2e:local": "bun run ./scripts/e2e.ts --local",
|
|
14
|
+
"test:e2e:prod": "bun run ./scripts/e2e.ts --prod",
|
|
12
15
|
"create": "service create",
|
|
13
16
|
"deploy": "service deploy",
|
|
14
17
|
"protect-main": "service protect-main",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: dev migrate migrate-lint gen lint test create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
1
|
+
.PHONY: dev migrate migrate-lint gen lint test test-e2e test-e2e-local test-e2e-prod create deploy protect-main dashboards observability-bootstrap auth destroy
|
|
2
2
|
|
|
3
3
|
SERVICE := service
|
|
4
4
|
WITH_ENV := set -a; [ ! -f .env.local ] || . ./.env.local; set +a;
|
|
@@ -28,6 +28,15 @@ lint:
|
|
|
28
28
|
test:
|
|
29
29
|
bun test ./test
|
|
30
30
|
|
|
31
|
+
test-e2e:
|
|
32
|
+
bun run ./scripts/e2e.ts $(ARGS)
|
|
33
|
+
|
|
34
|
+
test-e2e-local:
|
|
35
|
+
bun run ./scripts/e2e.ts --local $(ARGS)
|
|
36
|
+
|
|
37
|
+
test-e2e-prod:
|
|
38
|
+
bun run ./scripts/e2e.ts --prod $(ARGS)
|
|
39
|
+
|
|
31
40
|
create:
|
|
32
41
|
$(SERVICE) create
|
|
33
42
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"gen": "make gen",
|
|
10
10
|
"lint": "make lint",
|
|
11
11
|
"test": "make test",
|
|
12
|
+
"test:e2e": "bun run ./scripts/e2e.ts",
|
|
13
|
+
"test:e2e:local": "bun run ./scripts/e2e.ts --local",
|
|
14
|
+
"test:e2e:prod": "bun run ./scripts/e2e.ts --prod",
|
|
12
15
|
"create": "service create",
|
|
13
16
|
"deploy": "service deploy",
|
|
14
17
|
"protect-main": "service protect-main",
|