@decocms/start 4.3.0 → 4.4.0
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/bun.lock +36 -110
- package/package.json +4 -3
- package/scripts/migrate/phase-report.ts +8 -0
- package/scripts/migrate/templates/server-entry.ts +39 -3
- package/scripts/migrate-to-cf-observability.test.ts +169 -0
- package/scripts/migrate-to-cf-observability.ts +611 -0
- package/src/sdk/logger.test.ts +79 -0
- package/src/sdk/logger.ts +40 -2
- package/src/sdk/otel.test.ts +128 -15
- package/src/sdk/otel.ts +179 -98
- package/src/sdk/sampler.ts +17 -5
- package/src/sdk/workerEntry.ts +7 -4
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for the `migrate-to-cf-observability.ts` codemod.
|
|
3
|
+
*
|
|
4
|
+
* Drives the script as a child process against tmp wrangler.jsonc fixtures.
|
|
5
|
+
* Verifies the three behaviors that matter operationally:
|
|
6
|
+
* - replacing an existing `observability.logs` block (lebiscuit shape)
|
|
7
|
+
* - appending a new `observability` block when none exists
|
|
8
|
+
* - second run is a no-op (idempotency / CI guard)
|
|
9
|
+
* - result is valid JSONC (parses after stripping comments)
|
|
10
|
+
*/
|
|
11
|
+
import * as cp from "node:child_process";
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
16
|
+
|
|
17
|
+
const SCRIPT = path.resolve(__dirname, "migrate-to-cf-observability.ts");
|
|
18
|
+
|
|
19
|
+
function runCodemod(args: string[]): { stdout: string; stderr: string; code: number } {
|
|
20
|
+
const r = cp.spawnSync("npx", ["tsx", SCRIPT, ...args], { encoding: "utf8" });
|
|
21
|
+
return { stdout: r.stdout || "", stderr: r.stderr || "", code: r.status ?? 0 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stripJsoncComments(s: string): string {
|
|
25
|
+
return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("migrate-to-cf-observability codemod", () => {
|
|
29
|
+
let tmpDir: string;
|
|
30
|
+
let wranglerPath: string;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cf-codemod-"));
|
|
34
|
+
wranglerPath = path.join(tmpDir, "wrangler.jsonc");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("replaces an existing observability.logs block in lebiscuit-shape config", () => {
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
wranglerPath,
|
|
44
|
+
`{
|
|
45
|
+
"name": "lebiscuit-tanstack",
|
|
46
|
+
"compatibility_date": "2026-02-14",
|
|
47
|
+
"main": "./src/worker-entry.ts",
|
|
48
|
+
"kv_namespaces": [{ "binding": "SITES_KV", "id": "abc" }],
|
|
49
|
+
"version_metadata": { "binding": "CF_VERSION_METADATA" },
|
|
50
|
+
"analytics_engine_datasets": [
|
|
51
|
+
{ "binding": "DECO_METRICS", "dataset": "deco_metrics_lebiscuit" }
|
|
52
|
+
],
|
|
53
|
+
"observability": {
|
|
54
|
+
"logs": {
|
|
55
|
+
"enabled": true,
|
|
56
|
+
"invocation_logs": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const r = runCodemod(["--source", tmpDir, "--write"]);
|
|
64
|
+
expect(r.code).toBe(0);
|
|
65
|
+
|
|
66
|
+
const result = fs.readFileSync(wranglerPath, "utf8");
|
|
67
|
+
expect(result).toContain('"destinations": ["hyperdx-logs"]');
|
|
68
|
+
expect(result).toContain('"destinations": ["hyperdx-traces"]');
|
|
69
|
+
expect(result).toContain('"head_sampling_rate": 0.1');
|
|
70
|
+
|
|
71
|
+
// Result must be valid JSONC.
|
|
72
|
+
expect(() => JSON.parse(stripJsoncComments(result))).not.toThrow();
|
|
73
|
+
|
|
74
|
+
// Original key context preserved.
|
|
75
|
+
expect(result).toContain('"name": "lebiscuit-tanstack"');
|
|
76
|
+
expect(result).toContain('"binding": "DECO_METRICS"');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("appends a new observability block when none exists", () => {
|
|
80
|
+
fs.writeFileSync(
|
|
81
|
+
wranglerPath,
|
|
82
|
+
`{
|
|
83
|
+
"name": "fresh-site",
|
|
84
|
+
"compatibility_date": "2026-02-14",
|
|
85
|
+
"main": "./src/worker-entry.ts"
|
|
86
|
+
}
|
|
87
|
+
`,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const r = runCodemod(["--source", tmpDir, "--write"]);
|
|
91
|
+
expect(r.code).toBe(0);
|
|
92
|
+
|
|
93
|
+
const result = fs.readFileSync(wranglerPath, "utf8");
|
|
94
|
+
expect(result).toContain('"observability"');
|
|
95
|
+
expect(result).toContain('"destinations": ["hyperdx-logs"]');
|
|
96
|
+
expect(() => JSON.parse(stripJsoncComments(result))).not.toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("is idempotent: second run is a no-op", () => {
|
|
100
|
+
fs.writeFileSync(
|
|
101
|
+
wranglerPath,
|
|
102
|
+
`{
|
|
103
|
+
"name": "lebiscuit-tanstack",
|
|
104
|
+
"main": "./src/worker-entry.ts",
|
|
105
|
+
"observability": {
|
|
106
|
+
"logs": { "enabled": true }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
runCodemod(["--source", tmpDir, "--write"]);
|
|
113
|
+
const after1 = fs.readFileSync(wranglerPath, "utf8");
|
|
114
|
+
|
|
115
|
+
const r = runCodemod(["--source", tmpDir, "--write"]);
|
|
116
|
+
expect(r.code).toBe(0);
|
|
117
|
+
expect(r.stdout).toContain("already on CF-native");
|
|
118
|
+
|
|
119
|
+
const after2 = fs.readFileSync(wranglerPath, "utf8");
|
|
120
|
+
expect(after2).toBe(after1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("dry-run exits 1 and does not modify the file (CI signal)", () => {
|
|
124
|
+
const before = `{
|
|
125
|
+
"name": "site",
|
|
126
|
+
"main": "./src/worker-entry.ts"
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
fs.writeFileSync(wranglerPath, before);
|
|
130
|
+
|
|
131
|
+
const r = runCodemod(["--source", tmpDir]);
|
|
132
|
+
expect(r.code).toBe(1);
|
|
133
|
+
expect(r.stdout).toContain("Dry-run");
|
|
134
|
+
|
|
135
|
+
expect(fs.readFileSync(wranglerPath, "utf8")).toBe(before);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("respects --logs / --traces / --traces-rate / --persist flags", () => {
|
|
139
|
+
fs.writeFileSync(
|
|
140
|
+
wranglerPath,
|
|
141
|
+
`{
|
|
142
|
+
"name": "site",
|
|
143
|
+
"main": "./src/worker-entry.ts"
|
|
144
|
+
}
|
|
145
|
+
`,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
runCodemod([
|
|
149
|
+
"--source",
|
|
150
|
+
tmpDir,
|
|
151
|
+
"--logs",
|
|
152
|
+
"my-logs",
|
|
153
|
+
"--traces",
|
|
154
|
+
"my-traces",
|
|
155
|
+
"--traces-rate",
|
|
156
|
+
"0.05",
|
|
157
|
+
"--persist",
|
|
158
|
+
"--write",
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const result = fs.readFileSync(wranglerPath, "utf8");
|
|
162
|
+
expect(result).toContain('"destinations": ["my-logs"]');
|
|
163
|
+
expect(result).toContain('"destinations": ["my-traces"]');
|
|
164
|
+
expect(result).toContain('"head_sampling_rate": 0.05');
|
|
165
|
+
// Both blocks set persist:true (no logs-only persist flag).
|
|
166
|
+
const persistTrueCount = (result.match(/"persist": true/g) ?? []).length;
|
|
167
|
+
expect(persistTrueCount).toBe(2);
|
|
168
|
+
});
|
|
169
|
+
}, 30_000);
|