blogwright-analytics 0.3.3
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/README.md +162 -0
- package/dist/adapters/duckdb-ingest.d.ts +76 -0
- package/dist/adapters/duckdb-ingest.js +173 -0
- package/dist/adapters/duckdb-query.d.ts +56 -0
- package/dist/adapters/duckdb-query.js +80 -0
- package/dist/adapters/duckdb-session.d.ts +168 -0
- package/dist/adapters/duckdb-session.js +330 -0
- package/dist/app/_app/immutable/assets/0.BTQrrh5B.css +1 -0
- package/dist/app/_app/immutable/assets/2.CZSK3rT8.css +1 -0
- package/dist/app/_app/immutable/assets/BrushContext.D7c8UPey.css +1 -0
- package/dist/app/_app/immutable/assets/ChartAnnotations.CPxIG7Mw.css +1 -0
- package/dist/app/_app/immutable/assets/Circle.C5MKzgk2.css +1 -0
- package/dist/app/_app/immutable/assets/DefaultTooltip.C5-uctZ7.css +1 -0
- package/dist/app/_app/immutable/assets/Group.DV48xipa.css +1 -0
- package/dist/app/_app/immutable/assets/Labels.BxZ4NUVz.css +1 -0
- package/dist/app/_app/immutable/assets/Legend.CxnrE4Ye.css +1 -0
- package/dist/app/_app/immutable/assets/Line.fkmsECm9.css +1 -0
- package/dist/app/_app/immutable/assets/Path.CvpwNZ6g.css +1 -0
- package/dist/app/_app/immutable/assets/Rect.CtRaGMmQ.css +1 -0
- package/dist/app/_app/immutable/assets/Text.j9l35qB0.css +1 -0
- package/dist/app/_app/immutable/assets/TransformContext.Bs_HkpAk.css +1 -0
- package/dist/app/_app/immutable/assets/Voronoi.ce7atosu.css +1 -0
- package/dist/app/_app/immutable/chunks/-aNGNaBT.js +1 -0
- package/dist/app/_app/immutable/chunks/6djn-yLs.js +1 -0
- package/dist/app/_app/immutable/chunks/B1amyutE.js +1 -0
- package/dist/app/_app/immutable/chunks/B3vZDoek.js +1 -0
- package/dist/app/_app/immutable/chunks/B5KRA4hC.js +1 -0
- package/dist/app/_app/immutable/chunks/BClnVG6H.js +1 -0
- package/dist/app/_app/immutable/chunks/BID1NNRh.js +1 -0
- package/dist/app/_app/immutable/chunks/BR2LaRms.js +1 -0
- package/dist/app/_app/immutable/chunks/Bd1gDe3Y.js +1 -0
- package/dist/app/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/dist/app/_app/immutable/chunks/Bl052uUt.js +1 -0
- package/dist/app/_app/immutable/chunks/Bye3lL0c.js +1 -0
- package/dist/app/_app/immutable/chunks/C58PZtCD.js +4 -0
- package/dist/app/_app/immutable/chunks/CAzydqEO.js +1 -0
- package/dist/app/_app/immutable/chunks/CCch3uox.js +1 -0
- package/dist/app/_app/immutable/chunks/CIlSMUH9.js +1 -0
- package/dist/app/_app/immutable/chunks/CO1vUXfR.js +1 -0
- package/dist/app/_app/immutable/chunks/CPbD8C65.js +5 -0
- package/dist/app/_app/immutable/chunks/CRTcXoMo.js +1 -0
- package/dist/app/_app/immutable/chunks/CjjyIQAO.js +1 -0
- package/dist/app/_app/immutable/chunks/CuXAxjvF.js +1 -0
- package/dist/app/_app/immutable/chunks/CvyVA_jC.js +1 -0
- package/dist/app/_app/immutable/chunks/CxGCFVdy.js +1 -0
- package/dist/app/_app/immutable/chunks/D0Ty6LN0.js +1 -0
- package/dist/app/_app/immutable/chunks/D2AaQUUW.js +1 -0
- package/dist/app/_app/immutable/chunks/D2BnX0Uk.js +3 -0
- package/dist/app/_app/immutable/chunks/DJc8C0NK.js +1 -0
- package/dist/app/_app/immutable/chunks/DKMlMI4a.js +1 -0
- package/dist/app/_app/immutable/chunks/DVXZkpbf.js +1 -0
- package/dist/app/_app/immutable/chunks/DVt8ukQ_.js +1 -0
- package/dist/app/_app/immutable/chunks/DZPlYdq_.js +1 -0
- package/dist/app/_app/immutable/chunks/Db0q5_zr.js +1 -0
- package/dist/app/_app/immutable/chunks/Dfvzj6n2.js +1 -0
- package/dist/app/_app/immutable/chunks/Dh958be7.js +1 -0
- package/dist/app/_app/immutable/chunks/DjKLLdnY.js +15 -0
- package/dist/app/_app/immutable/chunks/Doz7YX1W.js +1 -0
- package/dist/app/_app/immutable/chunks/DthYhn6Y.js +2 -0
- package/dist/app/_app/immutable/chunks/DtuTIrAM.js +1 -0
- package/dist/app/_app/immutable/chunks/HclGiUj8.js +1 -0
- package/dist/app/_app/immutable/chunks/Hx0TNsV3.js +1 -0
- package/dist/app/_app/immutable/chunks/RobXhXPM.js +1 -0
- package/dist/app/_app/immutable/chunks/V9ZjaxiY.js +1 -0
- package/dist/app/_app/immutable/chunks/Y5urAfNy.js +1 -0
- package/dist/app/_app/immutable/chunks/caXkbKD3.js +1 -0
- package/dist/app/_app/immutable/chunks/devYm2ud.js +1 -0
- package/dist/app/_app/immutable/chunks/mtZWP0zR.js +1 -0
- package/dist/app/_app/immutable/chunks/vDgBJUjM.js +1 -0
- package/dist/app/_app/immutable/chunks/xIq_fFFM.js +1 -0
- package/dist/app/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/app/_app/immutable/chunks/z05MoCFz.js +1 -0
- package/dist/app/_app/immutable/entry/app.CLAerUAN.js +2 -0
- package/dist/app/_app/immutable/entry/start.D3MqnNci.js +1 -0
- package/dist/app/_app/immutable/nodes/0.UTMEigHJ.js +1 -0
- package/dist/app/_app/immutable/nodes/1.Cn4f11bT.js +1 -0
- package/dist/app/_app/immutable/nodes/2.B39cIcr2.js +6 -0
- package/dist/app/_app/version.json +1 -0
- package/dist/app/index.html +82 -0
- package/dist/aws/clients.d.ts +70 -0
- package/dist/aws/clients.js +52 -0
- package/dist/aws/errors.d.ts +41 -0
- package/dist/aws/errors.js +70 -0
- package/dist/aws/firehose.d.ts +228 -0
- package/dist/aws/firehose.js +347 -0
- package/dist/aws/glue.d.ts +103 -0
- package/dist/aws/glue.js +225 -0
- package/dist/aws/lambda.d.ts +132 -0
- package/dist/aws/lambda.js +339 -0
- package/dist/aws/s3tables.d.ts +120 -0
- package/dist/aws/s3tables.js +281 -0
- package/dist/backfill.d.ts +100 -0
- package/dist/backfill.js +294 -0
- package/dist/commands.d.ts +124 -0
- package/dist/commands.js +336 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +317 -0
- package/dist/fixture-ingest.d.ts +49 -0
- package/dist/fixture-ingest.js +43 -0
- package/dist/fixture-query.d.ts +39 -0
- package/dist/fixture-query.js +70 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/nodes.d.ts +404 -0
- package/dist/nodes.js +2708 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.js +47 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +248 -0
- package/dist/ports.d.ts +113 -0
- package/dist/ports.js +35 -0
- package/dist/queries.d.ts +301 -0
- package/dist/queries.js +414 -0
- package/dist/schema.d.ts +240 -0
- package/dist/schema.js +154 -0
- package/dist/server.d.ts +150 -0
- package/dist/server.js +499 -0
- package/dist/transform/bots.d.ts +47 -0
- package/dist/transform/bots.js +73 -0
- package/dist/transform/handler.d.ts +135 -0
- package/dist/transform/handler.js +177 -0
- package/dist/transform/map-record.d.ts +110 -0
- package/dist/transform/map-record.js +275 -0
- package/dist/transform/visitor-key.d.ts +83 -0
- package/dist/transform/visitor-key.js +120 -0
- package/dist/transform-bundle/index.mjs +21456 -0
- package/dist/transform-bundle/transform-manifest.json +4 -0
- package/dist/transform-hash.d.ts +135 -0
- package/dist/transform-hash.js +186 -0
- package/dist/write-transform-manifest.mjs +365 -0
- package/package.json +59 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transform Lambda's build identity: a reproducible hash of the *source*
|
|
3
|
+
* the function is bundled from, the artifact names that hash is stamped
|
|
4
|
+
* beside, and the one derivation of the zip key that hash produces.
|
|
5
|
+
*
|
|
6
|
+
* This is step 5 of
|
|
7
|
+
* [§Implementation notes](../../../.specs/changes/merged/2026-07-26-analytics_plugin.md) -
|
|
8
|
+
* "The hash keys the uploaded zip so identical source never redeploys the
|
|
9
|
+
* function" - and it exists as its own module so `nodes.ts` (task 50) consumes
|
|
10
|
+
* a derivation rather than restating a key format. A key spelled twice is a key
|
|
11
|
+
* that can disagree with itself, and the disagreement is invisible: the
|
|
12
|
+
* function silently redeploys on every reconcile, or worse, never redeploys
|
|
13
|
+
* after a real source change.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the *source*, never the bundle
|
|
16
|
+
*
|
|
17
|
+
* `DEVELOPMENT.md` §Repository hygiene states the rule this module obeys: "The
|
|
18
|
+
* build-agent manifest hashes the agent's *source*, not the built bundle, so
|
|
19
|
+
* image keys do not vary by platform." Bundler output varies with the
|
|
20
|
+
* toolchain and the host (macOS laptop vs the Linux CI runner); source bytes do
|
|
21
|
+
* not. Hashing the bundle would key the same code under two different zip keys
|
|
22
|
+
* depending on who ran `pnpm build`, redeploying the function on every
|
|
23
|
+
* platform switch for no change at all. So the bundle path appears nowhere in
|
|
24
|
+
* the input list below - which is also why `packages/analytics/src/aws/lambda.ts`
|
|
25
|
+
* (task 36) deliberately does not surface `CodeSha256`: that digests the zip
|
|
26
|
+
* Lambda holds, and comparing it to this hash would compare two different
|
|
27
|
+
* things.
|
|
28
|
+
*
|
|
29
|
+
* ## What is in the hash, and why each input is there
|
|
30
|
+
*
|
|
31
|
+
* The shape follows `agentSourceHash` (`packages/build-agent/src/agent-hash.ts`)
|
|
32
|
+
* exactly: collect, label, sort by label with a codepoint comparison, and
|
|
33
|
+
* digest each input as `label` NUL `bytes` NUL. The framing matters - without
|
|
34
|
+
* the labels a file rename would leave the hash unchanged, and without the NUL
|
|
35
|
+
* separators two adjacent inputs could be split differently and collide.
|
|
36
|
+
*
|
|
37
|
+
* The inputs are a deliberate superset of what rolldown actually tree-shakes
|
|
38
|
+
* into the bundle: an unrelated change to this package or to core can force a
|
|
39
|
+
* (harmless) redeploy, which is the right trade against ever shipping a stale
|
|
40
|
+
* transform. `core/src` is in because the bundle genuinely inlines it - the
|
|
41
|
+
* entry constructs core's `SigningClient` and `SecretsManagerClient` - and the
|
|
42
|
+
* lockfile, `tsconfig.json` and the rolldown config are in because each of them
|
|
43
|
+
* changes the emitted bundle without changing a single line of this package's
|
|
44
|
+
* own source. The rolldown config lives at `src/transform/rolldown.config.ts`,
|
|
45
|
+
* so the `analytics/src` collection below already carries it; it is not listed
|
|
46
|
+
* a second time, because hashing the same bytes under two labels adds nothing.
|
|
47
|
+
* `transform-hash.test.ts` proves each of those inputs is live by changing one
|
|
48
|
+
* byte of it and asserting the hash moves.
|
|
49
|
+
*
|
|
50
|
+
* Test files are excluded (the `.test.ts` filter, as in `agentSourceHash`):
|
|
51
|
+
* they are never bundled, so a test-only edit must not redeploy the function.
|
|
52
|
+
*
|
|
53
|
+
* ## No direct filesystem call here
|
|
54
|
+
*
|
|
55
|
+
* Reading crosses the {@link FileSystem} port rather than Node's `fs` module,
|
|
56
|
+
* so this stays a domain module under DEVELOPMENT.md §Hexagonal architecture
|
|
57
|
+
* and no `packages/analytics/src/` path has to join the `no-restricted-imports`
|
|
58
|
+
* override list in `.oxlintrc.json`. The real adapter is constructed in
|
|
59
|
+
* `transform/write-manifest.ts`, the build-time edge that runs this.
|
|
60
|
+
*/
|
|
61
|
+
import type { FileSystem } from 'blogwright-core';
|
|
62
|
+
/**
|
|
63
|
+
* Where the build puts the Lambda artifacts, relative to the package root.
|
|
64
|
+
* Its own directory, not `dist/transform/` (which `tsc` fills with the
|
|
65
|
+
* unbundled modules), so the bundle can never be overwritten by the compiler.
|
|
66
|
+
*
|
|
67
|
+
* It holds two files, and they are not both deployment artifacts:
|
|
68
|
+
* {@link TRANSFORM_BUNDLE_FILE} is the zip's single entry, and
|
|
69
|
+
* {@link TRANSFORM_MANIFEST_FILE} is read *beside* the zip at deploy time for
|
|
70
|
+
* the hash and the key it derives. Task 50 zips the bundle file alone - see
|
|
71
|
+
* {@link TRANSFORM_BUNDLE_FILE} for why a one-file zip is the shape the
|
|
72
|
+
* `Handler` string assumes - and never the directory wholesale.
|
|
73
|
+
*/
|
|
74
|
+
export declare const TRANSFORM_BUNDLE_DIR = "dist/transform-bundle";
|
|
75
|
+
/**
|
|
76
|
+
* The bundle's file name inside {@link TRANSFORM_BUNDLE_DIR} - and, unchanged,
|
|
77
|
+
* inside the zip task 50 uploads.
|
|
78
|
+
*
|
|
79
|
+
* `.mjs`, not `.js`: the bundle is ESM, and the Lambda Node runtime reads a
|
|
80
|
+
* `.js` file in the deployment package as CommonJS unless the zip also carries
|
|
81
|
+
* a `package.json` declaring `"type": "module"`. A one-file zip with a `.mjs`
|
|
82
|
+
* extension needs no such companion, so there is no second file to forget - and
|
|
83
|
+
* this file, alone, is what that zip contains.
|
|
84
|
+
*
|
|
85
|
+
* That the emitted bundle really is ESM exporting this module's binding is
|
|
86
|
+
* asserted by `transform/write-manifest.ts` on every build, because no test
|
|
87
|
+
* sees the emitted file.
|
|
88
|
+
*/
|
|
89
|
+
export declare const TRANSFORM_BUNDLE_FILE = "index.mjs";
|
|
90
|
+
/**
|
|
91
|
+
* The build-time manifest carrying the hash and the key it derives, written
|
|
92
|
+
* beside the bundle in {@link TRANSFORM_BUNDLE_DIR} and read beside the zip -
|
|
93
|
+
* never packed inside it.
|
|
94
|
+
*/
|
|
95
|
+
export declare const TRANSFORM_MANIFEST_FILE = "transform-manifest.json";
|
|
96
|
+
/**
|
|
97
|
+
* The `Handler` string the function is configured with (task 50), spelled here
|
|
98
|
+
* because it is derived from this module's artifact names and nothing else:
|
|
99
|
+
* the bundle's base name, then the binding `transform/entry.ts` exports.
|
|
100
|
+
*
|
|
101
|
+
* It is here rather than in `nodes.ts` because getting it wrong fails at
|
|
102
|
+
* *invoke* time with an AWS-side error and no build error - every record would
|
|
103
|
+
* land in the Firehose error prefix with nothing in this repo reporting it, and
|
|
104
|
+
* the only symptom is an empty dashboard. Two checks pin this constant against
|
|
105
|
+
* a real export rather than a comment: `transform/entry.test.ts` against the
|
|
106
|
+
* entry *module*'s, and `transform/write-manifest.ts` against the emitted
|
|
107
|
+
* *bundle*'s, on every build. A rename or a bundler-config change reddens one
|
|
108
|
+
* of them instead of silently emptying the warehouse.
|
|
109
|
+
*/
|
|
110
|
+
export declare const TRANSFORM_LAMBDA_HANDLER = "index.handler";
|
|
111
|
+
/**
|
|
112
|
+
* A hash of the transform's *source*, computed at bundle time and stamped into
|
|
113
|
+
* `dist/transform-bundle/transform-manifest.json` (see
|
|
114
|
+
* `transform/write-manifest.ts`) so the plugin reads it at runtime without any
|
|
115
|
+
* access to the source tree.
|
|
116
|
+
*
|
|
117
|
+
* `dir` is the analytics package root; core and the workspace root are located
|
|
118
|
+
* from it the way `agentSourceHash` locates its own siblings. Only labels,
|
|
119
|
+
* never absolute paths, reach the digest, so the same tree checked out at two
|
|
120
|
+
* different paths hashes identically - a property `transform-hash.test.ts`
|
|
121
|
+
* asserts, because a hash that moves with the checkout directory would key the
|
|
122
|
+
* same code under a different zip on every machine.
|
|
123
|
+
*/
|
|
124
|
+
export declare function transformSourceHash(dir: string, fs: FileSystem): Promise<string>;
|
|
125
|
+
/**
|
|
126
|
+
* The key the bundled transform's zip is stored and compared under, derived
|
|
127
|
+
* from {@link transformSourceHash} and spelled in this one place. Task 50's
|
|
128
|
+
* `analytics-transform-function` records it and skips the code update when it
|
|
129
|
+
* has not moved, so identical source provably maps to an identical key.
|
|
130
|
+
*
|
|
131
|
+
* A malformed hash raises rather than producing a key: `transform-undefined.zip`
|
|
132
|
+
* is a key that compares equal to itself forever, which would pin the deployed
|
|
133
|
+
* function at whatever code first shipped and never update it again.
|
|
134
|
+
*/
|
|
135
|
+
export declare function transformZipKey(hash: string): string;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transform Lambda's build identity: a reproducible hash of the *source*
|
|
3
|
+
* the function is bundled from, the artifact names that hash is stamped
|
|
4
|
+
* beside, and the one derivation of the zip key that hash produces.
|
|
5
|
+
*
|
|
6
|
+
* This is step 5 of
|
|
7
|
+
* [§Implementation notes](../../../.specs/changes/merged/2026-07-26-analytics_plugin.md) -
|
|
8
|
+
* "The hash keys the uploaded zip so identical source never redeploys the
|
|
9
|
+
* function" - and it exists as its own module so `nodes.ts` (task 50) consumes
|
|
10
|
+
* a derivation rather than restating a key format. A key spelled twice is a key
|
|
11
|
+
* that can disagree with itself, and the disagreement is invisible: the
|
|
12
|
+
* function silently redeploys on every reconcile, or worse, never redeploys
|
|
13
|
+
* after a real source change.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the *source*, never the bundle
|
|
16
|
+
*
|
|
17
|
+
* `DEVELOPMENT.md` §Repository hygiene states the rule this module obeys: "The
|
|
18
|
+
* build-agent manifest hashes the agent's *source*, not the built bundle, so
|
|
19
|
+
* image keys do not vary by platform." Bundler output varies with the
|
|
20
|
+
* toolchain and the host (macOS laptop vs the Linux CI runner); source bytes do
|
|
21
|
+
* not. Hashing the bundle would key the same code under two different zip keys
|
|
22
|
+
* depending on who ran `pnpm build`, redeploying the function on every
|
|
23
|
+
* platform switch for no change at all. So the bundle path appears nowhere in
|
|
24
|
+
* the input list below - which is also why `packages/analytics/src/aws/lambda.ts`
|
|
25
|
+
* (task 36) deliberately does not surface `CodeSha256`: that digests the zip
|
|
26
|
+
* Lambda holds, and comparing it to this hash would compare two different
|
|
27
|
+
* things.
|
|
28
|
+
*
|
|
29
|
+
* ## What is in the hash, and why each input is there
|
|
30
|
+
*
|
|
31
|
+
* The shape follows `agentSourceHash` (`packages/build-agent/src/agent-hash.ts`)
|
|
32
|
+
* exactly: collect, label, sort by label with a codepoint comparison, and
|
|
33
|
+
* digest each input as `label` NUL `bytes` NUL. The framing matters - without
|
|
34
|
+
* the labels a file rename would leave the hash unchanged, and without the NUL
|
|
35
|
+
* separators two adjacent inputs could be split differently and collide.
|
|
36
|
+
*
|
|
37
|
+
* The inputs are a deliberate superset of what rolldown actually tree-shakes
|
|
38
|
+
* into the bundle: an unrelated change to this package or to core can force a
|
|
39
|
+
* (harmless) redeploy, which is the right trade against ever shipping a stale
|
|
40
|
+
* transform. `core/src` is in because the bundle genuinely inlines it - the
|
|
41
|
+
* entry constructs core's `SigningClient` and `SecretsManagerClient` - and the
|
|
42
|
+
* lockfile, `tsconfig.json` and the rolldown config are in because each of them
|
|
43
|
+
* changes the emitted bundle without changing a single line of this package's
|
|
44
|
+
* own source. The rolldown config lives at `src/transform/rolldown.config.ts`,
|
|
45
|
+
* so the `analytics/src` collection below already carries it; it is not listed
|
|
46
|
+
* a second time, because hashing the same bytes under two labels adds nothing.
|
|
47
|
+
* `transform-hash.test.ts` proves each of those inputs is live by changing one
|
|
48
|
+
* byte of it and asserting the hash moves.
|
|
49
|
+
*
|
|
50
|
+
* Test files are excluded (the `.test.ts` filter, as in `agentSourceHash`):
|
|
51
|
+
* they are never bundled, so a test-only edit must not redeploy the function.
|
|
52
|
+
*
|
|
53
|
+
* ## No direct filesystem call here
|
|
54
|
+
*
|
|
55
|
+
* Reading crosses the {@link FileSystem} port rather than Node's `fs` module,
|
|
56
|
+
* so this stays a domain module under DEVELOPMENT.md §Hexagonal architecture
|
|
57
|
+
* and no `packages/analytics/src/` path has to join the `no-restricted-imports`
|
|
58
|
+
* override list in `.oxlintrc.json`. The real adapter is constructed in
|
|
59
|
+
* `transform/write-manifest.ts`, the build-time edge that runs this.
|
|
60
|
+
*/
|
|
61
|
+
import { createHash } from 'node:crypto';
|
|
62
|
+
import { join, sep } from 'node:path';
|
|
63
|
+
/**
|
|
64
|
+
* Hex characters kept from the SHA-256 digest. Twelve, as `agentSourceHash`
|
|
65
|
+
* slices to: enough that a collision between two revisions of one small source
|
|
66
|
+
* tree is not a practical concern, short enough to read in a resource name.
|
|
67
|
+
*/
|
|
68
|
+
const HASH_LENGTH = 12;
|
|
69
|
+
/** A well-formed {@link transformSourceHash} result. */
|
|
70
|
+
const HASH_PATTERN = new RegExp(`^[0-9a-f]{${HASH_LENGTH}}$`);
|
|
71
|
+
/**
|
|
72
|
+
* Where the build puts the Lambda artifacts, relative to the package root.
|
|
73
|
+
* Its own directory, not `dist/transform/` (which `tsc` fills with the
|
|
74
|
+
* unbundled modules), so the bundle can never be overwritten by the compiler.
|
|
75
|
+
*
|
|
76
|
+
* It holds two files, and they are not both deployment artifacts:
|
|
77
|
+
* {@link TRANSFORM_BUNDLE_FILE} is the zip's single entry, and
|
|
78
|
+
* {@link TRANSFORM_MANIFEST_FILE} is read *beside* the zip at deploy time for
|
|
79
|
+
* the hash and the key it derives. Task 50 zips the bundle file alone - see
|
|
80
|
+
* {@link TRANSFORM_BUNDLE_FILE} for why a one-file zip is the shape the
|
|
81
|
+
* `Handler` string assumes - and never the directory wholesale.
|
|
82
|
+
*/
|
|
83
|
+
export const TRANSFORM_BUNDLE_DIR = 'dist/transform-bundle';
|
|
84
|
+
/**
|
|
85
|
+
* The bundle's file name inside {@link TRANSFORM_BUNDLE_DIR} - and, unchanged,
|
|
86
|
+
* inside the zip task 50 uploads.
|
|
87
|
+
*
|
|
88
|
+
* `.mjs`, not `.js`: the bundle is ESM, and the Lambda Node runtime reads a
|
|
89
|
+
* `.js` file in the deployment package as CommonJS unless the zip also carries
|
|
90
|
+
* a `package.json` declaring `"type": "module"`. A one-file zip with a `.mjs`
|
|
91
|
+
* extension needs no such companion, so there is no second file to forget - and
|
|
92
|
+
* this file, alone, is what that zip contains.
|
|
93
|
+
*
|
|
94
|
+
* That the emitted bundle really is ESM exporting this module's binding is
|
|
95
|
+
* asserted by `transform/write-manifest.ts` on every build, because no test
|
|
96
|
+
* sees the emitted file.
|
|
97
|
+
*/
|
|
98
|
+
export const TRANSFORM_BUNDLE_FILE = 'index.mjs';
|
|
99
|
+
/**
|
|
100
|
+
* The build-time manifest carrying the hash and the key it derives, written
|
|
101
|
+
* beside the bundle in {@link TRANSFORM_BUNDLE_DIR} and read beside the zip -
|
|
102
|
+
* never packed inside it.
|
|
103
|
+
*/
|
|
104
|
+
export const TRANSFORM_MANIFEST_FILE = 'transform-manifest.json';
|
|
105
|
+
/**
|
|
106
|
+
* The `Handler` string the function is configured with (task 50), spelled here
|
|
107
|
+
* because it is derived from this module's artifact names and nothing else:
|
|
108
|
+
* the bundle's base name, then the binding `transform/entry.ts` exports.
|
|
109
|
+
*
|
|
110
|
+
* It is here rather than in `nodes.ts` because getting it wrong fails at
|
|
111
|
+
* *invoke* time with an AWS-side error and no build error - every record would
|
|
112
|
+
* land in the Firehose error prefix with nothing in this repo reporting it, and
|
|
113
|
+
* the only symptom is an empty dashboard. Two checks pin this constant against
|
|
114
|
+
* a real export rather than a comment: `transform/entry.test.ts` against the
|
|
115
|
+
* entry *module*'s, and `transform/write-manifest.ts` against the emitted
|
|
116
|
+
* *bundle*'s, on every build. A rename or a bundler-config change reddens one
|
|
117
|
+
* of them instead of silently emptying the warehouse.
|
|
118
|
+
*/
|
|
119
|
+
export const TRANSFORM_LAMBDA_HANDLER = 'index.handler';
|
|
120
|
+
/**
|
|
121
|
+
* Every non-test file under `root`, labelled by its path under `prefix`.
|
|
122
|
+
*
|
|
123
|
+
* `listFiles` is contracted to return sorted, `root`-relative paths, so the
|
|
124
|
+
* collection order does not depend on the host filesystem's readdir order;
|
|
125
|
+
* separators are normalised to `/` so a label is the same on every platform.
|
|
126
|
+
*/
|
|
127
|
+
async function collectSource(fs, root, prefix) {
|
|
128
|
+
const files = await fs.listFiles(root);
|
|
129
|
+
return files
|
|
130
|
+
.filter((relativePath) => !relativePath.endsWith('.test.ts'))
|
|
131
|
+
.map((relativePath) => ({
|
|
132
|
+
label: `${prefix}/${relativePath.split(sep).join('/')}`,
|
|
133
|
+
path: join(root, relativePath),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A hash of the transform's *source*, computed at bundle time and stamped into
|
|
138
|
+
* `dist/transform-bundle/transform-manifest.json` (see
|
|
139
|
+
* `transform/write-manifest.ts`) so the plugin reads it at runtime without any
|
|
140
|
+
* access to the source tree.
|
|
141
|
+
*
|
|
142
|
+
* `dir` is the analytics package root; core and the workspace root are located
|
|
143
|
+
* from it the way `agentSourceHash` locates its own siblings. Only labels,
|
|
144
|
+
* never absolute paths, reach the digest, so the same tree checked out at two
|
|
145
|
+
* different paths hashes identically - a property `transform-hash.test.ts`
|
|
146
|
+
* asserts, because a hash that moves with the checkout directory would key the
|
|
147
|
+
* same code under a different zip on every machine.
|
|
148
|
+
*/
|
|
149
|
+
export async function transformSourceHash(dir, fs) {
|
|
150
|
+
const coreDir = join(dir, '..', 'core');
|
|
151
|
+
const rootDir = join(dir, '..', '..');
|
|
152
|
+
const inputs = [
|
|
153
|
+
...(await collectSource(fs, join(dir, 'src'), 'analytics/src')),
|
|
154
|
+
...(await collectSource(fs, join(coreDir, 'src'), 'core/src')),
|
|
155
|
+
{ label: 'analytics/package.json', path: join(dir, 'package.json') },
|
|
156
|
+
{ label: 'analytics/tsconfig.json', path: join(dir, 'tsconfig.json') },
|
|
157
|
+
{ label: 'core/package.json', path: join(coreDir, 'package.json') },
|
|
158
|
+
{ label: 'workspace/tsconfig.base.json', path: join(rootDir, 'tsconfig.base.json') },
|
|
159
|
+
{ label: 'workspace/pnpm-lock.yaml', path: join(rootDir, 'pnpm-lock.yaml') },
|
|
160
|
+
// Codepoint sort, not localeCompare: collation must not depend on host locale/ICU.
|
|
161
|
+
].sort((a, b) => (a.label < b.label ? -1 : 1));
|
|
162
|
+
const digest = createHash('sha256');
|
|
163
|
+
for (const { label, path } of inputs) {
|
|
164
|
+
digest.update(label);
|
|
165
|
+
digest.update('\0');
|
|
166
|
+
digest.update(await fs.readBytes(path));
|
|
167
|
+
digest.update('\0');
|
|
168
|
+
}
|
|
169
|
+
return digest.digest('hex').slice(0, HASH_LENGTH);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The key the bundled transform's zip is stored and compared under, derived
|
|
173
|
+
* from {@link transformSourceHash} and spelled in this one place. Task 50's
|
|
174
|
+
* `analytics-transform-function` records it and skips the code update when it
|
|
175
|
+
* has not moved, so identical source provably maps to an identical key.
|
|
176
|
+
*
|
|
177
|
+
* A malformed hash raises rather than producing a key: `transform-undefined.zip`
|
|
178
|
+
* is a key that compares equal to itself forever, which would pin the deployed
|
|
179
|
+
* function at whatever code first shipped and never update it again.
|
|
180
|
+
*/
|
|
181
|
+
export function transformZipKey(hash) {
|
|
182
|
+
if (!HASH_PATTERN.test(hash)) {
|
|
183
|
+
throw new Error(`the analytics transform's source hash must be ${HASH_LENGTH} lowercase hex characters, not "${hash}" - rebuild the package so ${TRANSFORM_MANIFEST_FILE} is regenerated`);
|
|
184
|
+
}
|
|
185
|
+
return `analytics/transform/transform-${hash}.zip`;
|
|
186
|
+
}
|