@codedrifters/constructs 0.0.72 → 0.0.74
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/lib/index.d.mts +131 -2
- package/lib/index.d.ts +132 -3
- package/lib/index.js +218 -9
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +211 -8
- package/lib/index.mjs.map +1 -1
- package/package.json +4 -2
package/lib/index.d.mts
CHANGED
|
@@ -1,10 +1,139 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodejsFunction, NodejsFunctionProps, ICommandHooks } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
2
2
|
import { Construct } from 'constructs';
|
|
3
|
+
import { Bucket, BucketProps } from 'aws-cdk-lib/aws-s3';
|
|
3
4
|
import { StackProps } from 'aws-cdk-lib';
|
|
4
5
|
import { HostedZoneAttributes } from 'aws-cdk-lib/aws-route53';
|
|
5
6
|
import { HostingMode } from './static-hosting.viewer-request-handler.mjs';
|
|
6
7
|
import 'aws-lambda';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Props for `PnpmWorkspaceNodejsFunction`. Extends
|
|
11
|
+
* `NodejsFunctionProps` so the wrapper is a drop-in for any existing
|
|
12
|
+
* `NodejsFunction` call site.
|
|
13
|
+
*/
|
|
14
|
+
interface PnpmWorkspaceNodejsFunctionProps extends NodejsFunctionProps {
|
|
15
|
+
/**
|
|
16
|
+
* Directory to begin the upward search for `pnpm-workspace.yaml`.
|
|
17
|
+
* Defaults to the directory of the resolved entry path.
|
|
18
|
+
*/
|
|
19
|
+
readonly workspaceSearchFrom?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Skip the policy-mirror step entirely. Useful for tests or when the
|
|
22
|
+
* caller wants the pure `NodejsFunction` behavior.
|
|
23
|
+
*
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
readonly disablePnpmPolicyMirror?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Result of `mirrorPnpmWorkspacePolicy` — describes what the policy
|
|
30
|
+
* mirror did for one entry.
|
|
31
|
+
*/
|
|
32
|
+
interface MirrorPnpmWorkspacePolicyResult {
|
|
33
|
+
/** Resolved `.npmrc` path if a write happened, otherwise undefined. */
|
|
34
|
+
readonly npmrcPath?: string;
|
|
35
|
+
/** Reason the mirror was a no-op, if it was. */
|
|
36
|
+
readonly skippedReason?: "missing-entry" | "missing-workspace-file" | "empty-policy" | "no-change";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mirror the workspace pnpm policy into an `.npmrc` adjacent to the
|
|
40
|
+
* Lambda entry. Pure side-effect helper used by
|
|
41
|
+
* `PnpmWorkspaceNodejsFunction`; exported so consumers can drive it
|
|
42
|
+
* from other call sites if desired.
|
|
43
|
+
*/
|
|
44
|
+
declare const mirrorPnpmWorkspacePolicy: (params: {
|
|
45
|
+
readonly entry: string;
|
|
46
|
+
readonly workspaceSearchFrom?: string;
|
|
47
|
+
}) => MirrorPnpmWorkspacePolicyResult;
|
|
48
|
+
/**
|
|
49
|
+
* Build an `ICommandHooks` implementation that copies an
|
|
50
|
+
* entry-adjacent `.npmrc` into the bundling input directory before
|
|
51
|
+
* `pnpm install` runs. Composes with any caller-supplied hooks so
|
|
52
|
+
* existing `beforeBundling` / `beforeInstall` / `afterBundling`
|
|
53
|
+
* commands are preserved.
|
|
54
|
+
*
|
|
55
|
+
* Exported for unit testing; the construct wires this automatically.
|
|
56
|
+
*/
|
|
57
|
+
declare const buildNpmrcCopyCommandHooks: (params: {
|
|
58
|
+
readonly npmrcPath: string;
|
|
59
|
+
readonly existingHooks?: ICommandHooks;
|
|
60
|
+
}) => ICommandHooks;
|
|
61
|
+
/**
|
|
62
|
+
* A `NodejsFunction` wrapper that mirrors the workspace's pnpm policy
|
|
63
|
+
* (currently `minimumReleaseAge` and `minimumReleaseAgeExclude`) into
|
|
64
|
+
* an `.npmrc` adjacent to the Lambda entry before bundling.
|
|
65
|
+
*
|
|
66
|
+
* CDK's bundler runs `pnpm install` in an isolated temp directory
|
|
67
|
+
* outside the workspace, which means `pnpm-workspace.yaml` settings
|
|
68
|
+
* do not apply there. CDK does **not** copy arbitrary entry-adjacent
|
|
69
|
+
* files into the bundling input directory, so the construct also
|
|
70
|
+
* wires a `bundling.commandHooks.beforeInstall` hook that copies the
|
|
71
|
+
* rendered `.npmrc` into the bundling input directory immediately
|
|
72
|
+
* before `pnpm install` runs. Caller-supplied `commandHooks` are
|
|
73
|
+
* preserved — the copy command is appended to whatever the caller
|
|
74
|
+
* already returns from `beforeInstall`.
|
|
75
|
+
*
|
|
76
|
+
* See the `pnpm-workspace-nodejs-function` documentation page for
|
|
77
|
+
* full details on what gets mirrored, merge behaviour against an
|
|
78
|
+
* existing `.npmrc`, and when to disable the mirror.
|
|
79
|
+
*/
|
|
80
|
+
declare class PnpmWorkspaceNodejsFunction extends NodejsFunction {
|
|
81
|
+
constructor(scope: Construct, id: string, props: PnpmWorkspaceNodejsFunctionProps);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Subset of `pnpm-workspace.yaml` fields that map to `.npmrc` keys
|
|
86
|
+
* pnpm honours when running install in a non-workspace directory.
|
|
87
|
+
*/
|
|
88
|
+
interface PnpmWorkspacePolicy {
|
|
89
|
+
/** `minimumReleaseAge` (minutes) — equivalent to `.npmrc` `minimum-release-age`. */
|
|
90
|
+
readonly minimumReleaseAge?: number;
|
|
91
|
+
/**
|
|
92
|
+
* `minimumReleaseAgeExclude` — equivalent to `.npmrc`
|
|
93
|
+
* `minimum-release-age-exclude` (comma-separated when serialised).
|
|
94
|
+
*/
|
|
95
|
+
readonly minimumReleaseAgeExclude?: ReadonlyArray<string>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Walk up from `startDir` until a `pnpm-workspace.yaml` file is found
|
|
99
|
+
* and return its absolute path. Returns `undefined` when the filesystem
|
|
100
|
+
* root is reached without finding one.
|
|
101
|
+
*/
|
|
102
|
+
declare const findPnpmWorkspaceFile: (startDir: string) => string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Read the policy subset (`minimumReleaseAge` and
|
|
105
|
+
* `minimumReleaseAgeExclude`) from a `pnpm-workspace.yaml` file.
|
|
106
|
+
*
|
|
107
|
+
* Returns an empty object when the file is missing, unreadable, or
|
|
108
|
+
* contains neither key. Throws when the file is present but YAML
|
|
109
|
+
* parsing fails — the caller is expected to surface a clear error.
|
|
110
|
+
*/
|
|
111
|
+
declare const readPnpmWorkspacePolicy: (workspaceFile: string) => PnpmWorkspacePolicy;
|
|
112
|
+
/**
|
|
113
|
+
* Render an `.npmrc` body that pnpm will honour when installing in
|
|
114
|
+
* a directory outside the workspace tree. The keys emitted here are
|
|
115
|
+
* the `.npmrc` equivalents of the `pnpm-workspace.yaml` fields in
|
|
116
|
+
* {@link PnpmWorkspacePolicy} — pnpm reads `minimum-release-age` and
|
|
117
|
+
* `minimum-release-age-exclude` from `.npmrc`, not from the workspace
|
|
118
|
+
* file, when the install runs outside the workspace.
|
|
119
|
+
*
|
|
120
|
+
* Returns an empty string when the policy carries no relevant fields.
|
|
121
|
+
*/
|
|
122
|
+
declare const renderNpmrcLines: (policy: PnpmWorkspacePolicy) => ReadonlyArray<string>;
|
|
123
|
+
/**
|
|
124
|
+
* Parse the body of an existing `.npmrc` file into a map of trimmed
|
|
125
|
+
* key/value pairs. Lines that are blank or start with `#` are dropped.
|
|
126
|
+
* Lines without an `=` are dropped.
|
|
127
|
+
*/
|
|
128
|
+
declare const parseNpmrc: (body: string) => Map<string, string>;
|
|
129
|
+
/**
|
|
130
|
+
* Merge a set of policy-derived `.npmrc` lines into an existing
|
|
131
|
+
* `.npmrc` body. Our keys overwrite matching keys in the existing
|
|
132
|
+
* body; all other keys are preserved. Returns the new `.npmrc` body
|
|
133
|
+
* (always terminated by a trailing newline when non-empty).
|
|
134
|
+
*/
|
|
135
|
+
declare const mergeNpmrc: (existing: string | undefined, ourLines: ReadonlyArray<string>) => string;
|
|
136
|
+
|
|
8
137
|
interface PrivateBucketProps extends BucketProps {
|
|
9
138
|
}
|
|
10
139
|
declare class PrivateBucket extends Bucket {
|
|
@@ -123,4 +252,4 @@ declare class StaticHosting extends Construct {
|
|
|
123
252
|
constructor(scope: Construct, id: string, props?: StaticHostingProps);
|
|
124
253
|
}
|
|
125
254
|
|
|
126
|
-
export { PrivateBucket, type PrivateBucketProps, StaticContent, type StaticContentProps, type StaticDomainProps, StaticHosting, type StaticHostingProps };
|
|
255
|
+
export { type MirrorPnpmWorkspacePolicyResult, PnpmWorkspaceNodejsFunction, type PnpmWorkspaceNodejsFunctionProps, type PnpmWorkspacePolicy, PrivateBucket, type PrivateBucketProps, StaticContent, type StaticContentProps, type StaticDomainProps, StaticHosting, type StaticHostingProps, buildNpmrcCopyCommandHooks, findPnpmWorkspaceFile, mergeNpmrc, mirrorPnpmWorkspacePolicy, parseNpmrc, readPnpmWorkspacePolicy, renderNpmrcLines };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodejsFunction, NodejsFunctionProps, ICommandHooks } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
2
2
|
import { Construct } from 'constructs';
|
|
3
|
+
import { Bucket, BucketProps } from 'aws-cdk-lib/aws-s3';
|
|
3
4
|
import { StackProps } from 'aws-cdk-lib';
|
|
4
5
|
import { HostedZoneAttributes } from 'aws-cdk-lib/aws-route53';
|
|
5
6
|
|
|
@@ -15,6 +16,134 @@ import { HostedZoneAttributes } from 'aws-cdk-lib/aws-route53';
|
|
|
15
16
|
*/
|
|
16
17
|
type HostingMode = "spa" | "static";
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Props for `PnpmWorkspaceNodejsFunction`. Extends
|
|
21
|
+
* `NodejsFunctionProps` so the wrapper is a drop-in for any existing
|
|
22
|
+
* `NodejsFunction` call site.
|
|
23
|
+
*/
|
|
24
|
+
interface PnpmWorkspaceNodejsFunctionProps extends NodejsFunctionProps {
|
|
25
|
+
/**
|
|
26
|
+
* Directory to begin the upward search for `pnpm-workspace.yaml`.
|
|
27
|
+
* Defaults to the directory of the resolved entry path.
|
|
28
|
+
*/
|
|
29
|
+
readonly workspaceSearchFrom?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Skip the policy-mirror step entirely. Useful for tests or when the
|
|
32
|
+
* caller wants the pure `NodejsFunction` behavior.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
readonly disablePnpmPolicyMirror?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Result of `mirrorPnpmWorkspacePolicy` — describes what the policy
|
|
40
|
+
* mirror did for one entry.
|
|
41
|
+
*/
|
|
42
|
+
interface MirrorPnpmWorkspacePolicyResult {
|
|
43
|
+
/** Resolved `.npmrc` path if a write happened, otherwise undefined. */
|
|
44
|
+
readonly npmrcPath?: string;
|
|
45
|
+
/** Reason the mirror was a no-op, if it was. */
|
|
46
|
+
readonly skippedReason?: "missing-entry" | "missing-workspace-file" | "empty-policy" | "no-change";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Mirror the workspace pnpm policy into an `.npmrc` adjacent to the
|
|
50
|
+
* Lambda entry. Pure side-effect helper used by
|
|
51
|
+
* `PnpmWorkspaceNodejsFunction`; exported so consumers can drive it
|
|
52
|
+
* from other call sites if desired.
|
|
53
|
+
*/
|
|
54
|
+
declare const mirrorPnpmWorkspacePolicy: (params: {
|
|
55
|
+
readonly entry: string;
|
|
56
|
+
readonly workspaceSearchFrom?: string;
|
|
57
|
+
}) => MirrorPnpmWorkspacePolicyResult;
|
|
58
|
+
/**
|
|
59
|
+
* Build an `ICommandHooks` implementation that copies an
|
|
60
|
+
* entry-adjacent `.npmrc` into the bundling input directory before
|
|
61
|
+
* `pnpm install` runs. Composes with any caller-supplied hooks so
|
|
62
|
+
* existing `beforeBundling` / `beforeInstall` / `afterBundling`
|
|
63
|
+
* commands are preserved.
|
|
64
|
+
*
|
|
65
|
+
* Exported for unit testing; the construct wires this automatically.
|
|
66
|
+
*/
|
|
67
|
+
declare const buildNpmrcCopyCommandHooks: (params: {
|
|
68
|
+
readonly npmrcPath: string;
|
|
69
|
+
readonly existingHooks?: ICommandHooks;
|
|
70
|
+
}) => ICommandHooks;
|
|
71
|
+
/**
|
|
72
|
+
* A `NodejsFunction` wrapper that mirrors the workspace's pnpm policy
|
|
73
|
+
* (currently `minimumReleaseAge` and `minimumReleaseAgeExclude`) into
|
|
74
|
+
* an `.npmrc` adjacent to the Lambda entry before bundling.
|
|
75
|
+
*
|
|
76
|
+
* CDK's bundler runs `pnpm install` in an isolated temp directory
|
|
77
|
+
* outside the workspace, which means `pnpm-workspace.yaml` settings
|
|
78
|
+
* do not apply there. CDK does **not** copy arbitrary entry-adjacent
|
|
79
|
+
* files into the bundling input directory, so the construct also
|
|
80
|
+
* wires a `bundling.commandHooks.beforeInstall` hook that copies the
|
|
81
|
+
* rendered `.npmrc` into the bundling input directory immediately
|
|
82
|
+
* before `pnpm install` runs. Caller-supplied `commandHooks` are
|
|
83
|
+
* preserved — the copy command is appended to whatever the caller
|
|
84
|
+
* already returns from `beforeInstall`.
|
|
85
|
+
*
|
|
86
|
+
* See the `pnpm-workspace-nodejs-function` documentation page for
|
|
87
|
+
* full details on what gets mirrored, merge behaviour against an
|
|
88
|
+
* existing `.npmrc`, and when to disable the mirror.
|
|
89
|
+
*/
|
|
90
|
+
declare class PnpmWorkspaceNodejsFunction extends NodejsFunction {
|
|
91
|
+
constructor(scope: Construct, id: string, props: PnpmWorkspaceNodejsFunctionProps);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Subset of `pnpm-workspace.yaml` fields that map to `.npmrc` keys
|
|
96
|
+
* pnpm honours when running install in a non-workspace directory.
|
|
97
|
+
*/
|
|
98
|
+
interface PnpmWorkspacePolicy {
|
|
99
|
+
/** `minimumReleaseAge` (minutes) — equivalent to `.npmrc` `minimum-release-age`. */
|
|
100
|
+
readonly minimumReleaseAge?: number;
|
|
101
|
+
/**
|
|
102
|
+
* `minimumReleaseAgeExclude` — equivalent to `.npmrc`
|
|
103
|
+
* `minimum-release-age-exclude` (comma-separated when serialised).
|
|
104
|
+
*/
|
|
105
|
+
readonly minimumReleaseAgeExclude?: ReadonlyArray<string>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Walk up from `startDir` until a `pnpm-workspace.yaml` file is found
|
|
109
|
+
* and return its absolute path. Returns `undefined` when the filesystem
|
|
110
|
+
* root is reached without finding one.
|
|
111
|
+
*/
|
|
112
|
+
declare const findPnpmWorkspaceFile: (startDir: string) => string | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Read the policy subset (`minimumReleaseAge` and
|
|
115
|
+
* `minimumReleaseAgeExclude`) from a `pnpm-workspace.yaml` file.
|
|
116
|
+
*
|
|
117
|
+
* Returns an empty object when the file is missing, unreadable, or
|
|
118
|
+
* contains neither key. Throws when the file is present but YAML
|
|
119
|
+
* parsing fails — the caller is expected to surface a clear error.
|
|
120
|
+
*/
|
|
121
|
+
declare const readPnpmWorkspacePolicy: (workspaceFile: string) => PnpmWorkspacePolicy;
|
|
122
|
+
/**
|
|
123
|
+
* Render an `.npmrc` body that pnpm will honour when installing in
|
|
124
|
+
* a directory outside the workspace tree. The keys emitted here are
|
|
125
|
+
* the `.npmrc` equivalents of the `pnpm-workspace.yaml` fields in
|
|
126
|
+
* {@link PnpmWorkspacePolicy} — pnpm reads `minimum-release-age` and
|
|
127
|
+
* `minimum-release-age-exclude` from `.npmrc`, not from the workspace
|
|
128
|
+
* file, when the install runs outside the workspace.
|
|
129
|
+
*
|
|
130
|
+
* Returns an empty string when the policy carries no relevant fields.
|
|
131
|
+
*/
|
|
132
|
+
declare const renderNpmrcLines: (policy: PnpmWorkspacePolicy) => ReadonlyArray<string>;
|
|
133
|
+
/**
|
|
134
|
+
* Parse the body of an existing `.npmrc` file into a map of trimmed
|
|
135
|
+
* key/value pairs. Lines that are blank or start with `#` are dropped.
|
|
136
|
+
* Lines without an `=` are dropped.
|
|
137
|
+
*/
|
|
138
|
+
declare const parseNpmrc: (body: string) => Map<string, string>;
|
|
139
|
+
/**
|
|
140
|
+
* Merge a set of policy-derived `.npmrc` lines into an existing
|
|
141
|
+
* `.npmrc` body. Our keys overwrite matching keys in the existing
|
|
142
|
+
* body; all other keys are preserved. Returns the new `.npmrc` body
|
|
143
|
+
* (always terminated by a trailing newline when non-empty).
|
|
144
|
+
*/
|
|
145
|
+
declare const mergeNpmrc: (existing: string | undefined, ourLines: ReadonlyArray<string>) => string;
|
|
146
|
+
|
|
18
147
|
interface PrivateBucketProps extends BucketProps {
|
|
19
148
|
}
|
|
20
149
|
declare class PrivateBucket extends Bucket {
|
|
@@ -133,5 +262,5 @@ declare class StaticHosting extends Construct {
|
|
|
133
262
|
constructor(scope: Construct, id: string, props?: StaticHostingProps);
|
|
134
263
|
}
|
|
135
264
|
|
|
136
|
-
export { PrivateBucket, StaticContent, StaticHosting };
|
|
137
|
-
export type { PrivateBucketProps, StaticContentProps, StaticDomainProps, StaticHostingProps };
|
|
265
|
+
export { PnpmWorkspaceNodejsFunction, PrivateBucket, StaticContent, StaticHosting, buildNpmrcCopyCommandHooks, findPnpmWorkspaceFile, mergeNpmrc, mirrorPnpmWorkspacePolicy, parseNpmrc, readPnpmWorkspacePolicy, renderNpmrcLines };
|
|
266
|
+
export type { MirrorPnpmWorkspacePolicyResult, PnpmWorkspaceNodejsFunctionProps, PnpmWorkspacePolicy, PrivateBucketProps, StaticContentProps, StaticDomainProps, StaticHostingProps };
|
package/lib/index.js
CHANGED
|
@@ -175,12 +175,213 @@ var require_lib = __commonJS({
|
|
|
175
175
|
// src/index.ts
|
|
176
176
|
var src_exports = {};
|
|
177
177
|
__export(src_exports, {
|
|
178
|
+
PnpmWorkspaceNodejsFunction: () => PnpmWorkspaceNodejsFunction,
|
|
178
179
|
PrivateBucket: () => PrivateBucket,
|
|
179
180
|
StaticContent: () => StaticContent,
|
|
180
|
-
StaticHosting: () => StaticHosting
|
|
181
|
+
StaticHosting: () => StaticHosting,
|
|
182
|
+
buildNpmrcCopyCommandHooks: () => buildNpmrcCopyCommandHooks,
|
|
183
|
+
findPnpmWorkspaceFile: () => findPnpmWorkspaceFile,
|
|
184
|
+
mergeNpmrc: () => mergeNpmrc,
|
|
185
|
+
mirrorPnpmWorkspacePolicy: () => mirrorPnpmWorkspacePolicy,
|
|
186
|
+
parseNpmrc: () => parseNpmrc,
|
|
187
|
+
readPnpmWorkspacePolicy: () => readPnpmWorkspacePolicy,
|
|
188
|
+
renderNpmrcLines: () => renderNpmrcLines
|
|
181
189
|
});
|
|
182
190
|
module.exports = __toCommonJS(src_exports);
|
|
183
191
|
|
|
192
|
+
// src/lambda/pnpm-workspace-nodejs-function.ts
|
|
193
|
+
var fs2 = __toESM(require("fs"));
|
|
194
|
+
var path2 = __toESM(require("path"));
|
|
195
|
+
var import_aws_lambda_nodejs = require("aws-cdk-lib/aws-lambda-nodejs");
|
|
196
|
+
|
|
197
|
+
// src/lambda/pnpm-workspace-parser.ts
|
|
198
|
+
var fs = __toESM(require("fs"));
|
|
199
|
+
var path = __toESM(require("path"));
|
|
200
|
+
var yaml = __toESM(require("js-yaml"));
|
|
201
|
+
var findPnpmWorkspaceFile = (startDir) => {
|
|
202
|
+
let current = path.resolve(startDir);
|
|
203
|
+
while (true) {
|
|
204
|
+
const candidate = path.join(current, "pnpm-workspace.yaml");
|
|
205
|
+
if (fs.existsSync(candidate)) {
|
|
206
|
+
return candidate;
|
|
207
|
+
}
|
|
208
|
+
const parent = path.dirname(current);
|
|
209
|
+
if (parent === current) {
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
current = parent;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var readPnpmWorkspacePolicy = (workspaceFile) => {
|
|
216
|
+
if (!fs.existsSync(workspaceFile)) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
const raw = fs.readFileSync(workspaceFile, "utf8");
|
|
220
|
+
const parsed = yaml.load(raw);
|
|
221
|
+
if (!parsed || typeof parsed !== "object") {
|
|
222
|
+
return {};
|
|
223
|
+
}
|
|
224
|
+
const obj = parsed;
|
|
225
|
+
const policy = {};
|
|
226
|
+
const age = obj.minimumReleaseAge;
|
|
227
|
+
if (typeof age === "number" && Number.isFinite(age)) {
|
|
228
|
+
policy.minimumReleaseAge = age;
|
|
229
|
+
}
|
|
230
|
+
const exclude = obj.minimumReleaseAgeExclude;
|
|
231
|
+
if (Array.isArray(exclude)) {
|
|
232
|
+
const cleaned = exclude.filter(
|
|
233
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
234
|
+
);
|
|
235
|
+
if (cleaned.length > 0) {
|
|
236
|
+
policy.minimumReleaseAgeExclude = cleaned;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return policy;
|
|
240
|
+
};
|
|
241
|
+
var renderNpmrcLines = (policy) => {
|
|
242
|
+
const lines = [];
|
|
243
|
+
if (typeof policy.minimumReleaseAge === "number") {
|
|
244
|
+
lines.push(`minimum-release-age=${policy.minimumReleaseAge}`);
|
|
245
|
+
}
|
|
246
|
+
const exclude = policy.minimumReleaseAgeExclude ?? [];
|
|
247
|
+
if (exclude.length > 0) {
|
|
248
|
+
lines.push(`minimum-release-age-exclude=${exclude.join(",")}`);
|
|
249
|
+
}
|
|
250
|
+
return lines;
|
|
251
|
+
};
|
|
252
|
+
var parseNpmrc = (body) => {
|
|
253
|
+
const map = /* @__PURE__ */ new Map();
|
|
254
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
255
|
+
const line = rawLine.trim();
|
|
256
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const eq = line.indexOf("=");
|
|
260
|
+
if (eq < 0) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const key = line.slice(0, eq).trim();
|
|
264
|
+
const value = line.slice(eq + 1).trim();
|
|
265
|
+
if (key.length > 0) {
|
|
266
|
+
map.set(key, value);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return map;
|
|
270
|
+
};
|
|
271
|
+
var mergeNpmrc = (existing, ourLines) => {
|
|
272
|
+
if (ourLines.length === 0) {
|
|
273
|
+
return existing ?? "";
|
|
274
|
+
}
|
|
275
|
+
const ourMap = /* @__PURE__ */ new Map();
|
|
276
|
+
for (const line of ourLines) {
|
|
277
|
+
const eq = line.indexOf("=");
|
|
278
|
+
if (eq < 0) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
ourMap.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());
|
|
282
|
+
}
|
|
283
|
+
const existingMap = parseNpmrc(existing ?? "");
|
|
284
|
+
for (const [key, value] of ourMap) {
|
|
285
|
+
existingMap.set(key, value);
|
|
286
|
+
}
|
|
287
|
+
const orderedKeys = [];
|
|
288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
289
|
+
for (const rawLine of (existing ?? "").split(/\r?\n/)) {
|
|
290
|
+
const line = rawLine.trim();
|
|
291
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const eq = line.indexOf("=");
|
|
295
|
+
if (eq < 0) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const key = line.slice(0, eq).trim();
|
|
299
|
+
if (key.length > 0 && existingMap.has(key) && !seen.has(key)) {
|
|
300
|
+
orderedKeys.push(key);
|
|
301
|
+
seen.add(key);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const key of ourMap.keys()) {
|
|
305
|
+
if (!seen.has(key)) {
|
|
306
|
+
orderedKeys.push(key);
|
|
307
|
+
seen.add(key);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const body = orderedKeys.map((key) => `${key}=${existingMap.get(key)}`).join("\n");
|
|
311
|
+
return body.length === 0 ? "" : `${body}
|
|
312
|
+
`;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/lambda/pnpm-workspace-nodejs-function.ts
|
|
316
|
+
var mirrorPnpmWorkspacePolicy = (params) => {
|
|
317
|
+
const { entry, workspaceSearchFrom } = params;
|
|
318
|
+
if (!fs2.existsSync(entry)) {
|
|
319
|
+
return { skippedReason: "missing-entry" };
|
|
320
|
+
}
|
|
321
|
+
const entryDir = path2.dirname(path2.resolve(entry));
|
|
322
|
+
const searchFrom = workspaceSearchFrom ? path2.resolve(workspaceSearchFrom) : entryDir;
|
|
323
|
+
const workspaceFile = findPnpmWorkspaceFile(searchFrom);
|
|
324
|
+
if (!workspaceFile) {
|
|
325
|
+
return { skippedReason: "missing-workspace-file" };
|
|
326
|
+
}
|
|
327
|
+
const policy = readPnpmWorkspacePolicy(workspaceFile);
|
|
328
|
+
const lines = renderNpmrcLines(policy);
|
|
329
|
+
if (lines.length === 0) {
|
|
330
|
+
return { skippedReason: "empty-policy" };
|
|
331
|
+
}
|
|
332
|
+
const npmrcPath = path2.join(entryDir, ".npmrc");
|
|
333
|
+
const existing = fs2.existsSync(npmrcPath) ? fs2.readFileSync(npmrcPath, "utf8") : void 0;
|
|
334
|
+
const merged = mergeNpmrc(existing, lines);
|
|
335
|
+
if (merged === existing) {
|
|
336
|
+
return { npmrcPath, skippedReason: "no-change" };
|
|
337
|
+
}
|
|
338
|
+
fs2.writeFileSync(npmrcPath, merged);
|
|
339
|
+
return { npmrcPath };
|
|
340
|
+
};
|
|
341
|
+
var buildNpmrcCopyCommandHooks = (params) => {
|
|
342
|
+
const { npmrcPath, existingHooks } = params;
|
|
343
|
+
return {
|
|
344
|
+
beforeBundling(inputDir, outputDir) {
|
|
345
|
+
return existingHooks?.beforeBundling(inputDir, outputDir) ?? [];
|
|
346
|
+
},
|
|
347
|
+
beforeInstall(inputDir, outputDir) {
|
|
348
|
+
const callerCommands = existingHooks?.beforeInstall(inputDir, outputDir) ?? [];
|
|
349
|
+
const copyCommand = `cp ${npmrcPath} ${inputDir}/.npmrc`;
|
|
350
|
+
return [...callerCommands, copyCommand];
|
|
351
|
+
},
|
|
352
|
+
afterBundling(inputDir, outputDir) {
|
|
353
|
+
return existingHooks?.afterBundling(inputDir, outputDir) ?? [];
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
var PnpmWorkspaceNodejsFunction = class extends import_aws_lambda_nodejs.NodejsFunction {
|
|
358
|
+
constructor(scope, id, props) {
|
|
359
|
+
let mirrorResult;
|
|
360
|
+
if (!props.disablePnpmPolicyMirror && props.entry) {
|
|
361
|
+
mirrorResult = mirrorPnpmWorkspacePolicy({
|
|
362
|
+
entry: props.entry,
|
|
363
|
+
workspaceSearchFrom: props.workspaceSearchFrom
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const { disablePnpmPolicyMirror, workspaceSearchFrom, ...rest } = props;
|
|
367
|
+
void disablePnpmPolicyMirror;
|
|
368
|
+
void workspaceSearchFrom;
|
|
369
|
+
const npmrcPath = mirrorResult?.npmrcPath;
|
|
370
|
+
const shouldWireHook = npmrcPath !== void 0 && fs2.existsSync(npmrcPath);
|
|
371
|
+
const propsWithHook = shouldWireHook ? {
|
|
372
|
+
...rest,
|
|
373
|
+
bundling: {
|
|
374
|
+
...rest.bundling,
|
|
375
|
+
commandHooks: buildNpmrcCopyCommandHooks({
|
|
376
|
+
npmrcPath,
|
|
377
|
+
existingHooks: rest.bundling?.commandHooks
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
} : rest;
|
|
381
|
+
super(scope, id, propsWithHook);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
184
385
|
// src/s3/private-bucket.ts
|
|
185
386
|
var import_aws_cdk_lib = require("aws-cdk-lib");
|
|
186
387
|
var import_aws_s3 = require("aws-cdk-lib/aws-s3");
|
|
@@ -239,14 +440,14 @@ var StaticContent = class extends import_constructs.Construct {
|
|
|
239
440
|
};
|
|
240
441
|
|
|
241
442
|
// src/static-hosting/static-hosting.ts
|
|
242
|
-
var
|
|
243
|
-
var
|
|
443
|
+
var fs3 = __toESM(require("fs"));
|
|
444
|
+
var path3 = __toESM(require("path"));
|
|
244
445
|
var import_aws_cdk_lib2 = require("aws-cdk-lib");
|
|
245
446
|
var import_aws_certificatemanager = require("aws-cdk-lib/aws-certificatemanager");
|
|
246
447
|
var import_aws_cloudfront = require("aws-cdk-lib/aws-cloudfront");
|
|
247
448
|
var import_aws_cloudfront_origins = require("aws-cdk-lib/aws-cloudfront-origins");
|
|
248
449
|
var import_aws_lambda = require("aws-cdk-lib/aws-lambda");
|
|
249
|
-
var
|
|
450
|
+
var import_aws_lambda_nodejs2 = require("aws-cdk-lib/aws-lambda-nodejs");
|
|
250
451
|
var import_aws_logs = require("aws-cdk-lib/aws-logs");
|
|
251
452
|
var import_aws_route53 = require("aws-cdk-lib/aws-route53");
|
|
252
453
|
var import_aws_route53_targets = require("aws-cdk-lib/aws-route53-targets");
|
|
@@ -292,16 +493,16 @@ var StaticHosting = class extends import_constructs2.Construct {
|
|
|
292
493
|
})
|
|
293
494
|
});
|
|
294
495
|
}
|
|
295
|
-
const handlerJs =
|
|
496
|
+
const handlerJs = path3.join(
|
|
296
497
|
__dirname,
|
|
297
498
|
"static-hosting.viewer-request-handler.js"
|
|
298
499
|
);
|
|
299
|
-
const handlerTs =
|
|
500
|
+
const handlerTs = path3.join(
|
|
300
501
|
__dirname,
|
|
301
502
|
"static-hosting.viewer-request-handler.ts"
|
|
302
503
|
);
|
|
303
|
-
const handlerEntry =
|
|
304
|
-
const handler = new
|
|
504
|
+
const handlerEntry = fs3.existsSync(handlerJs) ? handlerJs : handlerTs;
|
|
505
|
+
const handler = new import_aws_lambda_nodejs2.NodejsFunction(this, "viewer-request-handler", {
|
|
305
506
|
entry: handlerEntry,
|
|
306
507
|
handler: hostingMode === "static" ? "staticHandler" : "spaHandler",
|
|
307
508
|
memorySize: 128,
|
|
@@ -383,8 +584,16 @@ var StaticHosting = class extends import_constructs2.Construct {
|
|
|
383
584
|
};
|
|
384
585
|
// Annotate the CommonJS export names for ESM import in node:
|
|
385
586
|
0 && (module.exports = {
|
|
587
|
+
PnpmWorkspaceNodejsFunction,
|
|
386
588
|
PrivateBucket,
|
|
387
589
|
StaticContent,
|
|
388
|
-
StaticHosting
|
|
590
|
+
StaticHosting,
|
|
591
|
+
buildNpmrcCopyCommandHooks,
|
|
592
|
+
findPnpmWorkspaceFile,
|
|
593
|
+
mergeNpmrc,
|
|
594
|
+
mirrorPnpmWorkspacePolicy,
|
|
595
|
+
parseNpmrc,
|
|
596
|
+
readPnpmWorkspacePolicy,
|
|
597
|
+
renderNpmrcLines
|
|
389
598
|
});
|
|
390
599
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString - string to hash\n * @param trimLength - trim to this length (defaults to 999 chars)\n * @returns Hex-encoded sha256 digest, truncated to `trimLength` characters.\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString - string to truncate\n * @param maxLength - max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","export * from \"./s3\";\nexport * from \"./static-hosting\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * `/stage.openhi.org/*` serves content to stage.openhi.org\n * `/feature-7.stage.openhi.org/*` serves content to feature-7.stage.openhi.org\n * `/pr-123.stage.openhi.org/*` serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.VITEST === \"true\";\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * `Lambda@Edge` handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html` so a\n * single-page app can serve its one root index and let the client-side\n * router handle the path.\n * - `static`: path-like URIs append `/index.html` (e.g. `/docs` →\n * `/docs/index.html`) so multi-page static sites can serve distinct\n * HTML per path.\n *\n * Multi-tenant domain-folder prepending runs after the rewrite in both\n * modes and is unaffected by this prop.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n hostingMode,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n hostingMode: \"spa\" as HostingMode,\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * `LAMBDA@EDGE` FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["exports","findGitBranch","exports","exports","exports","import_aws_s3","import_aws_cdk_lib","import_aws_ssm","import_constructs"]}
|
|
1
|
+
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/index.ts","../src/lambda/pnpm-workspace-nodejs-function.ts","../src/lambda/pnpm-workspace-parser.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString - string to hash\n * @param trimLength - trim to this length (defaults to 999 chars)\n * @returns Hex-encoded sha256 digest, truncated to `trimLength` characters.\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString - string to truncate\n * @param maxLength - max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","export * from \"./lambda\";\nexport * from \"./s3\";\nexport * from \"./static-hosting\";\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n ICommandHooks,\n NodejsFunction,\n NodejsFunctionProps,\n} from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n findPnpmWorkspaceFile,\n mergeNpmrc,\n readPnpmWorkspacePolicy,\n renderNpmrcLines,\n} from \"./pnpm-workspace-parser\";\n\n/**\n * Props for `PnpmWorkspaceNodejsFunction`. Extends\n * `NodejsFunctionProps` so the wrapper is a drop-in for any existing\n * `NodejsFunction` call site.\n */\nexport interface PnpmWorkspaceNodejsFunctionProps extends NodejsFunctionProps {\n /**\n * Directory to begin the upward search for `pnpm-workspace.yaml`.\n * Defaults to the directory of the resolved entry path.\n */\n readonly workspaceSearchFrom?: string;\n\n /**\n * Skip the policy-mirror step entirely. Useful for tests or when the\n * caller wants the pure `NodejsFunction` behavior.\n *\n * @default false\n */\n readonly disablePnpmPolicyMirror?: boolean;\n}\n\n/**\n * Result of `mirrorPnpmWorkspacePolicy` — describes what the policy\n * mirror did for one entry.\n */\nexport interface MirrorPnpmWorkspacePolicyResult {\n /** Resolved `.npmrc` path if a write happened, otherwise undefined. */\n readonly npmrcPath?: string;\n /** Reason the mirror was a no-op, if it was. */\n readonly skippedReason?:\n | \"missing-entry\"\n | \"missing-workspace-file\"\n | \"empty-policy\"\n | \"no-change\";\n}\n\n/**\n * Mirror the workspace pnpm policy into an `.npmrc` adjacent to the\n * Lambda entry. Pure side-effect helper used by\n * `PnpmWorkspaceNodejsFunction`; exported so consumers can drive it\n * from other call sites if desired.\n */\nexport const mirrorPnpmWorkspacePolicy = (params: {\n readonly entry: string;\n readonly workspaceSearchFrom?: string;\n}): MirrorPnpmWorkspacePolicyResult => {\n const { entry, workspaceSearchFrom } = params;\n if (!fs.existsSync(entry)) {\n return { skippedReason: \"missing-entry\" };\n }\n\n const entryDir = path.dirname(path.resolve(entry));\n const searchFrom = workspaceSearchFrom\n ? path.resolve(workspaceSearchFrom)\n : entryDir;\n\n const workspaceFile = findPnpmWorkspaceFile(searchFrom);\n if (!workspaceFile) {\n return { skippedReason: \"missing-workspace-file\" };\n }\n\n const policy = readPnpmWorkspacePolicy(workspaceFile);\n const lines = renderNpmrcLines(policy);\n if (lines.length === 0) {\n return { skippedReason: \"empty-policy\" };\n }\n\n const npmrcPath = path.join(entryDir, \".npmrc\");\n const existing = fs.existsSync(npmrcPath)\n ? fs.readFileSync(npmrcPath, \"utf8\")\n : undefined;\n const merged = mergeNpmrc(existing, lines);\n\n if (merged === existing) {\n return { npmrcPath, skippedReason: \"no-change\" };\n }\n\n fs.writeFileSync(npmrcPath, merged);\n return { npmrcPath };\n};\n\n/**\n * Build an `ICommandHooks` implementation that copies an\n * entry-adjacent `.npmrc` into the bundling input directory before\n * `pnpm install` runs. Composes with any caller-supplied hooks so\n * existing `beforeBundling` / `beforeInstall` / `afterBundling`\n * commands are preserved.\n *\n * Exported for unit testing; the construct wires this automatically.\n */\nexport const buildNpmrcCopyCommandHooks = (params: {\n readonly npmrcPath: string;\n readonly existingHooks?: ICommandHooks;\n}): ICommandHooks => {\n const { npmrcPath, existingHooks } = params;\n return {\n beforeBundling(inputDir: string, outputDir: string): string[] {\n return existingHooks?.beforeBundling(inputDir, outputDir) ?? [];\n },\n beforeInstall(inputDir: string, outputDir: string): string[] {\n const callerCommands =\n existingHooks?.beforeInstall(inputDir, outputDir) ?? [];\n const copyCommand = `cp ${npmrcPath} ${inputDir}/.npmrc`;\n return [...callerCommands, copyCommand];\n },\n afterBundling(inputDir: string, outputDir: string): string[] {\n return existingHooks?.afterBundling(inputDir, outputDir) ?? [];\n },\n };\n};\n\n/**\n * A `NodejsFunction` wrapper that mirrors the workspace's pnpm policy\n * (currently `minimumReleaseAge` and `minimumReleaseAgeExclude`) into\n * an `.npmrc` adjacent to the Lambda entry before bundling.\n *\n * CDK's bundler runs `pnpm install` in an isolated temp directory\n * outside the workspace, which means `pnpm-workspace.yaml` settings\n * do not apply there. CDK does **not** copy arbitrary entry-adjacent\n * files into the bundling input directory, so the construct also\n * wires a `bundling.commandHooks.beforeInstall` hook that copies the\n * rendered `.npmrc` into the bundling input directory immediately\n * before `pnpm install` runs. Caller-supplied `commandHooks` are\n * preserved — the copy command is appended to whatever the caller\n * already returns from `beforeInstall`.\n *\n * See the `pnpm-workspace-nodejs-function` documentation page for\n * full details on what gets mirrored, merge behaviour against an\n * existing `.npmrc`, and when to disable the mirror.\n */\nexport class PnpmWorkspaceNodejsFunction extends NodejsFunction {\n constructor(\n scope: Construct,\n id: string,\n props: PnpmWorkspaceNodejsFunctionProps,\n ) {\n let mirrorResult: MirrorPnpmWorkspacePolicyResult | undefined;\n if (!props.disablePnpmPolicyMirror && props.entry) {\n mirrorResult = mirrorPnpmWorkspacePolicy({\n entry: props.entry,\n workspaceSearchFrom: props.workspaceSearchFrom,\n });\n }\n\n const { disablePnpmPolicyMirror, workspaceSearchFrom, ...rest } = props;\n void disablePnpmPolicyMirror;\n void workspaceSearchFrom;\n\n // Wire the beforeInstall hook only when the mirror produced (or\n // confirmed) an .npmrc file on disk. Other skippedReason values\n // (`missing-entry`, `missing-workspace-file`, `empty-policy`) mean\n // there is nothing to copy; falling through to plain NodejsFunction\n // behavior is correct in those cases.\n const npmrcPath = mirrorResult?.npmrcPath;\n const shouldWireHook = npmrcPath !== undefined && fs.existsSync(npmrcPath);\n\n const propsWithHook: NodejsFunctionProps = shouldWireHook\n ? {\n ...rest,\n bundling: {\n ...rest.bundling,\n commandHooks: buildNpmrcCopyCommandHooks({\n npmrcPath,\n existingHooks: rest.bundling?.commandHooks,\n }),\n },\n }\n : rest;\n\n super(scope, id, propsWithHook);\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Subset of `pnpm-workspace.yaml` fields that map to `.npmrc` keys\n * pnpm honours when running install in a non-workspace directory.\n */\nexport interface PnpmWorkspacePolicy {\n /** `minimumReleaseAge` (minutes) — equivalent to `.npmrc` `minimum-release-age`. */\n readonly minimumReleaseAge?: number;\n /**\n * `minimumReleaseAgeExclude` — equivalent to `.npmrc`\n * `minimum-release-age-exclude` (comma-separated when serialised).\n */\n readonly minimumReleaseAgeExclude?: ReadonlyArray<string>;\n}\n\n/**\n * Walk up from `startDir` until a `pnpm-workspace.yaml` file is found\n * and return its absolute path. Returns `undefined` when the filesystem\n * root is reached without finding one.\n */\nexport const findPnpmWorkspaceFile = (startDir: string): string | undefined => {\n let current = path.resolve(startDir);\n while (true) {\n const candidate = path.join(current, \"pnpm-workspace.yaml\");\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n const parent = path.dirname(current);\n if (parent === current) {\n return undefined;\n }\n current = parent;\n }\n};\n\n/**\n * Read the policy subset (`minimumReleaseAge` and\n * `minimumReleaseAgeExclude`) from a `pnpm-workspace.yaml` file.\n *\n * Returns an empty object when the file is missing, unreadable, or\n * contains neither key. Throws when the file is present but YAML\n * parsing fails — the caller is expected to surface a clear error.\n */\nexport const readPnpmWorkspacePolicy = (\n workspaceFile: string,\n): PnpmWorkspacePolicy => {\n if (!fs.existsSync(workspaceFile)) {\n return {};\n }\n\n const raw = fs.readFileSync(workspaceFile, \"utf8\");\n const parsed = yaml.load(raw);\n\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n\n const obj = parsed as Record<string, unknown>;\n const policy: {\n -readonly [K in keyof PnpmWorkspacePolicy]: PnpmWorkspacePolicy[K];\n } = {};\n\n const age = obj.minimumReleaseAge;\n if (typeof age === \"number\" && Number.isFinite(age)) {\n policy.minimumReleaseAge = age;\n }\n\n const exclude = obj.minimumReleaseAgeExclude;\n if (Array.isArray(exclude)) {\n const cleaned = exclude.filter(\n (entry): entry is string => typeof entry === \"string\" && entry.length > 0,\n );\n if (cleaned.length > 0) {\n policy.minimumReleaseAgeExclude = cleaned;\n }\n }\n\n return policy;\n};\n\n/**\n * Render an `.npmrc` body that pnpm will honour when installing in\n * a directory outside the workspace tree. The keys emitted here are\n * the `.npmrc` equivalents of the `pnpm-workspace.yaml` fields in\n * {@link PnpmWorkspacePolicy} — pnpm reads `minimum-release-age` and\n * `minimum-release-age-exclude` from `.npmrc`, not from the workspace\n * file, when the install runs outside the workspace.\n *\n * Returns an empty string when the policy carries no relevant fields.\n */\nexport const renderNpmrcLines = (\n policy: PnpmWorkspacePolicy,\n): ReadonlyArray<string> => {\n const lines: Array<string> = [];\n\n if (typeof policy.minimumReleaseAge === \"number\") {\n lines.push(`minimum-release-age=${policy.minimumReleaseAge}`);\n }\n\n const exclude = policy.minimumReleaseAgeExclude ?? [];\n if (exclude.length > 0) {\n lines.push(`minimum-release-age-exclude=${exclude.join(\",\")}`);\n }\n\n return lines;\n};\n\n/**\n * Parse the body of an existing `.npmrc` file into a map of trimmed\n * key/value pairs. Lines that are blank or start with `#` are dropped.\n * Lines without an `=` are dropped.\n */\nexport const parseNpmrc = (body: string): Map<string, string> => {\n const map = new Map<string, string>();\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith(\"#\")) {\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n const key = line.slice(0, eq).trim();\n const value = line.slice(eq + 1).trim();\n if (key.length > 0) {\n map.set(key, value);\n }\n }\n return map;\n};\n\n/**\n * Merge a set of policy-derived `.npmrc` lines into an existing\n * `.npmrc` body. Our keys overwrite matching keys in the existing\n * body; all other keys are preserved. Returns the new `.npmrc` body\n * (always terminated by a trailing newline when non-empty).\n */\nexport const mergeNpmrc = (\n existing: string | undefined,\n ourLines: ReadonlyArray<string>,\n): string => {\n if (ourLines.length === 0) {\n return existing ?? \"\";\n }\n\n const ourMap = new Map<string, string>();\n for (const line of ourLines) {\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n ourMap.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());\n }\n\n const existingMap = parseNpmrc(existing ?? \"\");\n for (const [key, value] of ourMap) {\n existingMap.set(key, value);\n }\n\n const orderedKeys: Array<string> = [];\n const seen = new Set<string>();\n\n // Preserve the original line order for any pre-existing keys.\n for (const rawLine of (existing ?? \"\").split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith(\"#\")) {\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n const key = line.slice(0, eq).trim();\n if (key.length > 0 && existingMap.has(key) && !seen.has(key)) {\n orderedKeys.push(key);\n seen.add(key);\n }\n }\n\n // Append any keys we added that weren't already present.\n for (const key of ourMap.keys()) {\n if (!seen.has(key)) {\n orderedKeys.push(key);\n seen.add(key);\n }\n }\n\n const body = orderedKeys\n .map((key) => `${key}=${existingMap.get(key)}`)\n .join(\"\\n\");\n\n return body.length === 0 ? \"\" : `${body}\\n`;\n};\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * `/stage.openhi.org/*` serves content to stage.openhi.org\n * `/feature-7.stage.openhi.org/*` serves content to feature-7.stage.openhi.org\n * `/pr-123.stage.openhi.org/*` serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.VITEST === \"true\";\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * `Lambda@Edge` handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html` so a\n * single-page app can serve its one root index and let the client-side\n * router handle the path.\n * - `static`: path-like URIs append `/index.html` (e.g. `/docs` →\n * `/docs/index.html`) so multi-page static sites can serve distinct\n * HTML per path.\n *\n * Multi-tenant domain-folder prepending runs after the rewrite in both\n * modes and is unaffected by this prop.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n hostingMode,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n hostingMode: \"spa\" as HostingMode,\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * `LAMBDA@EDGE` FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKa,IAAAA,SAAA,iBAAiB;;;;MAI5B,KAAK;;;;MAKL,OAAO;;;;MAKP,MAAM;;AAYK,IAAAA,SAAA,yBAAyB;;;;;MAKpC,SAAS;;;;;MAKT,WAAW;;AAcA,IAAAA,SAAA,uBAAuBA,SAAA;;;;;;;;;;ACvDpC,QAAA,uBAAA,QAAA,eAAA;AAQO,QAAMC,iBAAgB,MAAa;AACxC,cAAO,GAAA,qBAAA,UAAS,iCAAiC,EAC9C,SAAS,MAAM,EACf,QAAQ,cAAc,EAAE;IAC7B;AAJa,IAAAC,SAAA,gBAAaD;AAMnB,QAAM,kBAAkB,MAAa;AAI1C,UAAI,QAAQ,IAAI,mBAAmB;AACjC,eAAO,QAAQ,IAAI;MACrB;AAKA,YAAM,UAAS,GAAA,qBAAA,UAAS,oCAAoC,EACzD,SAAS,MAAM,EACf,QAAQ,cAAc,EAAE,EACxB,KAAI;AAEP,YAAM,QAAQ,OAAO,MAAM,iCAAiC;AAC5D,YAAM,WAAW,QAAQ,MAAM,CAAC,IAAI;AAEpC,aAAO;IACT;AApBa,IAAAC,SAAA,kBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACd5B,QAAA,SAAA,aAAA,QAAA,QAAA,CAAA;AAQO,QAAM,aAAa,CAAC,UAAkB,aAAqB,QAAO;AACvE,aAAO,OACJ,WAAW,QAAQ,EACnB,OAAO,QAAQ,EACf,OAAO,KAAK,EACZ,UAAU,GAAG,UAAU;IAC5B;AANa,IAAAC,SAAA,aAAU;AAchB,QAAM,mBAAmB,CAAC,aAAqB,cAAqB;AACzE,aAAO,YAAY,SAAS,YACxB,cACA,YAAY,UAAU,GAAG,SAAS;IACxC;AAJa,IAAAA,SAAA,mBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;ACtB7B,iBAAA,qBAAAC,QAAA;AACA,iBAAA,qBAAAA,QAAA;AACA,iBAAA,wBAAAA,QAAA;;;;;ACFA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,+BAIO;;;ACNP,SAAoB;AACpB,WAAsB;AACtB,WAAsB;AAqBf,IAAM,wBAAwB,CAAC,aAAyC;AAC7E,MAAI,UAAe,aAAQ,QAAQ;AACnC,SAAO,MAAM;AACX,UAAM,YAAiB,UAAK,SAAS,qBAAqB;AAC1D,QAAO,cAAW,SAAS,GAAG;AAC5B,aAAO;AAAA,IACT;AACA,UAAM,SAAc,aAAQ,OAAO;AACnC,QAAI,WAAW,SAAS;AACtB,aAAO;AAAA,IACT;AACA,cAAU;AAAA,EACZ;AACF;AAUO,IAAM,0BAA0B,CACrC,kBACwB;AACxB,MAAI,CAAI,cAAW,aAAa,GAAG;AACjC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAS,gBAAa,eAAe,MAAM;AACjD,QAAM,SAAc,UAAK,GAAG;AAE5B,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAM;AACZ,QAAM,SAEF,CAAC;AAEL,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,GAAG;AACnD,WAAO,oBAAoB;AAAA,EAC7B;AAEA,QAAM,UAAU,IAAI;AACpB,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,UAAM,UAAU,QAAQ;AAAA,MACtB,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS;AAAA,IAC1E;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,2BAA2B;AAAA,IACpC;AAAA,EACF;AAEA,SAAO;AACT;AAYO,IAAM,mBAAmB,CAC9B,WAC0B;AAC1B,QAAM,QAAuB,CAAC;AAE9B,MAAI,OAAO,OAAO,sBAAsB,UAAU;AAChD,UAAM,KAAK,uBAAuB,OAAO,iBAAiB,EAAE;AAAA,EAC9D;AAEA,QAAM,UAAU,OAAO,4BAA4B,CAAC;AACpD,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,KAAK,+BAA+B,QAAQ,KAAK,GAAG,CAAC,EAAE;AAAA,EAC/D;AAEA,SAAO;AACT;AAOO,IAAM,aAAa,CAAC,SAAsC;AAC/D,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,WAAW,KAAK,MAAM,OAAO,GAAG;AACzC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,WAAW,KAAK,KAAK,WAAW,GAAG,GAAG;AAC7C;AAAA,IACF;AACA,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,GAAG;AACV;AAAA,IACF;AACA,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACnC,UAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK;AACtC,QAAI,IAAI,SAAS,GAAG;AAClB,UAAI,IAAI,KAAK,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAQO,IAAM,aAAa,CACxB,UACA,aACW;AACX,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,YAAY;AAAA,EACrB;AAEA,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,QAAQ,UAAU;AAC3B,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,GAAG;AACV;AAAA,IACF;AACA,WAAO,IAAI,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC;AAAA,EAChE;AAEA,QAAM,cAAc,WAAW,YAAY,EAAE;AAC7C,aAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,gBAAY,IAAI,KAAK,KAAK;AAAA,EAC5B;AAEA,QAAM,cAA6B,CAAC;AACpC,QAAM,OAAO,oBAAI,IAAY;AAG7B,aAAW,YAAY,YAAY,IAAI,MAAM,OAAO,GAAG;AACrD,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,WAAW,KAAK,KAAK,WAAW,GAAG,GAAG;AAC7C;AAAA,IACF;AACA,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,GAAG;AACV;AAAA,IACF;AACA,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACnC,QAAI,IAAI,SAAS,KAAK,YAAY,IAAI,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,GAAG;AAC5D,kBAAY,KAAK,GAAG;AACpB,WAAK,IAAI,GAAG;AAAA,IACd;AAAA,EACF;AAGA,aAAW,OAAO,OAAO,KAAK,GAAG;AAC/B,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,kBAAY,KAAK,GAAG;AACpB,WAAK,IAAI,GAAG;AAAA,IACd;AAAA,EACF;AAEA,QAAM,OAAO,YACV,IAAI,CAAC,QAAQ,GAAG,GAAG,IAAI,YAAY,IAAI,GAAG,CAAC,EAAE,EAC7C,KAAK,IAAI;AAEZ,SAAO,KAAK,WAAW,IAAI,KAAK,GAAG,IAAI;AAAA;AACzC;;;AD3IO,IAAM,4BAA4B,CAAC,WAGH;AACrC,QAAM,EAAE,OAAO,oBAAoB,IAAI;AACvC,MAAI,CAAI,eAAW,KAAK,GAAG;AACzB,WAAO,EAAE,eAAe,gBAAgB;AAAA,EAC1C;AAEA,QAAM,WAAgB,cAAa,cAAQ,KAAK,CAAC;AACjD,QAAM,aAAa,sBACV,cAAQ,mBAAmB,IAChC;AAEJ,QAAM,gBAAgB,sBAAsB,UAAU;AACtD,MAAI,CAAC,eAAe;AAClB,WAAO,EAAE,eAAe,yBAAyB;AAAA,EACnD;AAEA,QAAM,SAAS,wBAAwB,aAAa;AACpD,QAAM,QAAQ,iBAAiB,MAAM;AACrC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,eAAe,eAAe;AAAA,EACzC;AAEA,QAAM,YAAiB,WAAK,UAAU,QAAQ;AAC9C,QAAM,WAAc,eAAW,SAAS,IACjC,iBAAa,WAAW,MAAM,IACjC;AACJ,QAAM,SAAS,WAAW,UAAU,KAAK;AAEzC,MAAI,WAAW,UAAU;AACvB,WAAO,EAAE,WAAW,eAAe,YAAY;AAAA,EACjD;AAEA,EAAG,kBAAc,WAAW,MAAM;AAClC,SAAO,EAAE,UAAU;AACrB;AAWO,IAAM,6BAA6B,CAAC,WAGtB;AACnB,QAAM,EAAE,WAAW,cAAc,IAAI;AACrC,SAAO;AAAA,IACL,eAAe,UAAkB,WAA6B;AAC5D,aAAO,eAAe,eAAe,UAAU,SAAS,KAAK,CAAC;AAAA,IAChE;AAAA,IACA,cAAc,UAAkB,WAA6B;AAC3D,YAAM,iBACJ,eAAe,cAAc,UAAU,SAAS,KAAK,CAAC;AACxD,YAAM,cAAc,MAAM,SAAS,IAAI,QAAQ;AAC/C,aAAO,CAAC,GAAG,gBAAgB,WAAW;AAAA,IACxC;AAAA,IACA,cAAc,UAAkB,WAA6B;AAC3D,aAAO,eAAe,cAAc,UAAU,SAAS,KAAK,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;AAqBO,IAAM,8BAAN,cAA0C,wCAAe;AAAA,EAC9D,YACE,OACA,IACA,OACA;AACA,QAAI;AACJ,QAAI,CAAC,MAAM,2BAA2B,MAAM,OAAO;AACjD,qBAAe,0BAA0B;AAAA,QACvC,OAAO,MAAM;AAAA,QACb,qBAAqB,MAAM;AAAA,MAC7B,CAAC;AAAA,IACH;AAEA,UAAM,EAAE,yBAAyB,qBAAqB,GAAG,KAAK,IAAI;AAClE,SAAK;AACL,SAAK;AAOL,UAAM,YAAY,cAAc;AAChC,UAAM,iBAAiB,cAAc,UAAgB,eAAW,SAAS;AAEzE,UAAM,gBAAqC,iBACvC;AAAA,MACE,GAAG;AAAA,MACH,UAAU;AAAA,QACR,GAAG,KAAK;AAAA,QACR,cAAc,2BAA2B;AAAA,UACvC;AAAA,UACA,eAAe,KAAK,UAAU;AAAA,QAChC,CAAC;AAAA,MACH;AAAA,IACF,IACA;AAEJ,UAAM,OAAO,IAAI,aAAa;AAAA,EAChC;AACF;;;AE1LA,yBAA8B;AAC9B,oBAKO;AAKA,IAAM,gBAAN,cAA4B,qBAAO;AAAA,EACxC,YAAY,OAAkB,IAAY,QAA4B,CAAC,GAAG;AACxE,UAAM,eAAe;AAAA,MACnB,eAAe,MAAM,iBAAiB,iCAAc;AAAA,MACpD,mBAAmB,MAAM,kBAAkB,iCAAc;AAAA,IAC3D;AAEA,UAAM,gBAAgB;AAAA,MACpB,kBAAkB;AAAA,MAClB,mBAAmB,gCAAkB;AAAA,MACrC,YAAY;AAAA,MACZ,iBAAiB,8BAAgB;AAAA,IACnC;AAEA,UAAM,OAAO,IAAI,EAAE,GAAG,cAAc,GAAG,OAAO,GAAG,cAAc,CAAC;AAAA,EAClE;AACF;;;AC1BA,mBAA8B;AAC9B,IAAAC,iBAAuB;AACvB,+BAAyC;AACzC,qBAAgC;AAChC,yBAA0B;AAC1B,wBAA0B;AAqDnB,IAAM,gBAAN,cAA4B,4BAAU;AAAA,EAC3C,YAAY,OAAkB,IAAY,OAA2B;AACnE,UAAM,OAAO,EAAE;AAUf,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAAA,MACF,oBAAoB;AAAA,MACpB,eAAW,4BAAc;AAAA,MACzB,GAAG;AAAA,IACL;AAQA,UAAM,YAAY,KAAC,8BAAU,SAAS,GAAG,UAAU,EAAE,KAAK,GAAG;AAE7D,UAAM,YAAY,+BAAgB;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AACA,UAAM,SAAS,sBAAO,cAAc,MAAM,UAAU,SAAS;AAU7D,UAAM,YAAY,QAAQ,IAAI,WAAW;AACzC,UAAM,UAAU,YAAY,CAAC,IAAI,CAAC,gCAAO,MAAM,sBAAsB,CAAC;AAEtE,QAAI,0CAAiB,MAAM,UAAU;AAAA,MACnC;AAAA,MACA,mBAAmB;AAAA,MACnB,gBAAgB;AAAA,MAChB,sBAAsB,GAAG,SAAS,GAAG,2BAA2B;AAAA,IAClE,CAAC;AAAA,EACH;AACF;;;ACnHA,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,IAAAC,sBAAqC;AACrC,oCAGO;AACP,4BAYO;AACP,oCAA+B;AAC/B,wBAAwB;AACxB,IAAAC,4BAA+B;AAC/B,sBAAwC;AACxC,yBAMO;AACP,iCAAiC;AACjC,IAAAC,kBAAgC;AAChC,IAAAC,qBAA0B;AAoEnB,IAAM,gBAAN,cAA4B,6BAAU;AAAA,EAM3C,YAAY,OAAkB,IAAY,QAA4B,CAAC,GAAG;AACxE,UAAM,OAAO,EAAE;AAUf,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAAA,MACF,oBAAoB;AAAA,MACpB,6BAA6B;AAAA,MAC7B,yBAAyB;AAAA,MACzB,aAAa;AAAA,MACb,GAAG;AAAA,IACL;AAEA,UAAM,EAAE,YAAY,qBAAqB,IAAI,qBAAqB,CAAC;AAWnE,UAAM,SAAS,IAAI;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAWA,QAAI;AACJ,QAAI;AAEJ,QAAI,wBAAwB,YAAY;AACtC,aAAO,8BAAW;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,oBAAc,IAAI,0CAAY,MAAM,wBAAwB;AAAA,QAC1D,YAAY,KAAK,UAAU;AAAA,QAC3B,yBAAyB,CAAC,UAAU;AAAA,QACpC,YAAY,oDAAsB,iBAAiB;AAAA,UACjD,CAAC,KAAK,UAAU,EAAE,GAAG;AAAA,UACrB,CAAC,UAAU,GAAG;AAAA,QAChB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAcA,UAAM,YAAiB;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AACA,UAAM,YAAiB;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AACA,UAAM,eAAkB,eAAW,SAAS,IAAI,YAAY;AAE5D,UAAM,UAAU,IAAI,yCAAe,MAAM,0BAA0B;AAAA,MACjE,OAAO;AAAA,MACP,SAAS,gBAAgB,WAAW,kBAAkB;AAAA,MACtD,YAAY;AAAA,MACZ,SAAS,0BAAQ;AAAA,MACjB,UAAU,IAAI,yBAAS,MAAM,oCAAoC;AAAA,QAC/D,WAAW,8BAAc;AAAA,MAC3B,CAAC;AAAA,IACH,CAAC;AAUD,UAAM,cAAc,IAAI,kCAAY,MAAM,qBAAqB;AAAA,MAC7D,SAAS;AAAA,MACT,QAAQ,6BAAS,QAAQ,GAAG;AAAA,MAC5B,QAAQ,6BAAS,QAAQ,CAAC;AAAA,MAC1B,YAAY,6BAAS,QAAQ,EAAE;AAAA,MAC/B,gBAAgB,0CAAoB,KAAK;AAAA,MACzC,qBAAqB,+CAAyB,KAAK;AAAA,MACnD,gBAAgB,0CAAoB,KAAK;AAAA,MACzC,0BAA0B;AAAA,MAC1B,4BAA4B;AAAA,IAC9B,CAAC;AAED,UAAM,MAAM,IAAI,4CAAsB,MAAM,SAAS;AAAA,MACnD,SAAS,8BAAQ;AAAA,IACnB,CAAC;AACD,UAAM,SAAS,6CAAe,wBAAwB,QAAQ;AAAA,MAC5D,qBAAqB;AAAA,MACrB,oBAAoB,CAAC,kCAAY,IAAI;AAAA,IACvC,CAAC;AAED,UAAM,eAAe,IAAI,mCAAa,MAAM,2BAA2B;AAAA,MACrE,SAAS,oBAAoB,MAAM,eAAe,EAAE;AAAA;AAAA;AAAA;AAAA,MAKpD,GAAI,eAAe,aACf;AAAA,QACE;AAAA,QACA,aAAa,CAAC,YAAY,KAAK,UAAU,EAAE;AAAA,MAC7C,IACA,CAAC;AAAA,MAEL,iBAAiB;AAAA,QACf;AAAA,QACA,sBAAsB,2CAAqB;AAAA,QAC3C;AAAA,QACA,gBAAgB,qCAAe;AAAA,QAC/B,aAAa;AAAA,UACX;AAAA,YACE,iBAAiB,QAAQ;AAAA,YACzB,WAAW,0CAAoB;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,IACrB,CAAC;AAKD,SAAK,aACH,eAAe,aAAa,aAAa,aAAa;AAUxD,QAAI,MAAM;AACR,UAAI,2BAAQ,MAAM,kBAAkB;AAAA,QAClC;AAAA,QACA,YAAY,aAAa,aAAa;AAAA,QACtC,QAAQ,gCAAa,UAAU,IAAI,4CAAiB,YAAY,CAAC;AAAA,MACnE,CAAC;AAED,UAAI,2BAAQ,MAAM,gBAAgB;AAAA,QAChC;AAAA,QACA,YAAY,aAAa,KAAK,UAAU,KAAK;AAAA,QAC7C,QAAQ,gCAAa,UAAU,IAAI,4CAAiB,YAAY,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAUA,QAAI,gCAAgB,MAAM,eAAe;AAAA,MACvC,aAAa,8DAA8D,MAAM,eAAe,EAAE;AAAA,MAClG,eAAe;AAAA,MACf,aAAa,aAAa;AAAA,IAC5B,CAAC;AAED,QAAI,gCAAgB,MAAM,WAAW;AAAA,MACnC,aAAa,8DAA8D,MAAM,eAAe,EAAE;AAAA,MAClG,eAAe;AAAA,MACf,aAAa,aAAa;AAAA,IAC5B,CAAC;AAED,QAAI,gCAAgB,MAAM,cAAc;AAAA,MACtC,aAAa,gDAAgD,MAAM,eAAe,EAAE;AAAA,MACpF,eAAe;AAAA,MACf,aAAa,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AACF;","names":["exports","findGitBranch","exports","exports","exports","fs","path","import_aws_s3","fs","path","import_aws_cdk_lib","import_aws_lambda_nodejs","import_aws_ssm","import_constructs"]}
|
package/lib/index.mjs
CHANGED
|
@@ -146,6 +146,201 @@ var require_lib = __commonJS({
|
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
// src/lambda/pnpm-workspace-nodejs-function.ts
|
|
150
|
+
import * as fs2 from "fs";
|
|
151
|
+
import * as path2 from "path";
|
|
152
|
+
import {
|
|
153
|
+
NodejsFunction
|
|
154
|
+
} from "aws-cdk-lib/aws-lambda-nodejs";
|
|
155
|
+
|
|
156
|
+
// src/lambda/pnpm-workspace-parser.ts
|
|
157
|
+
import * as fs from "fs";
|
|
158
|
+
import * as path from "path";
|
|
159
|
+
import * as yaml from "js-yaml";
|
|
160
|
+
var findPnpmWorkspaceFile = (startDir) => {
|
|
161
|
+
let current = path.resolve(startDir);
|
|
162
|
+
while (true) {
|
|
163
|
+
const candidate = path.join(current, "pnpm-workspace.yaml");
|
|
164
|
+
if (fs.existsSync(candidate)) {
|
|
165
|
+
return candidate;
|
|
166
|
+
}
|
|
167
|
+
const parent = path.dirname(current);
|
|
168
|
+
if (parent === current) {
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
current = parent;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var readPnpmWorkspacePolicy = (workspaceFile) => {
|
|
175
|
+
if (!fs.existsSync(workspaceFile)) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
const raw = fs.readFileSync(workspaceFile, "utf8");
|
|
179
|
+
const parsed = yaml.load(raw);
|
|
180
|
+
if (!parsed || typeof parsed !== "object") {
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
const obj = parsed;
|
|
184
|
+
const policy = {};
|
|
185
|
+
const age = obj.minimumReleaseAge;
|
|
186
|
+
if (typeof age === "number" && Number.isFinite(age)) {
|
|
187
|
+
policy.minimumReleaseAge = age;
|
|
188
|
+
}
|
|
189
|
+
const exclude = obj.minimumReleaseAgeExclude;
|
|
190
|
+
if (Array.isArray(exclude)) {
|
|
191
|
+
const cleaned = exclude.filter(
|
|
192
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
193
|
+
);
|
|
194
|
+
if (cleaned.length > 0) {
|
|
195
|
+
policy.minimumReleaseAgeExclude = cleaned;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return policy;
|
|
199
|
+
};
|
|
200
|
+
var renderNpmrcLines = (policy) => {
|
|
201
|
+
const lines = [];
|
|
202
|
+
if (typeof policy.minimumReleaseAge === "number") {
|
|
203
|
+
lines.push(`minimum-release-age=${policy.minimumReleaseAge}`);
|
|
204
|
+
}
|
|
205
|
+
const exclude = policy.minimumReleaseAgeExclude ?? [];
|
|
206
|
+
if (exclude.length > 0) {
|
|
207
|
+
lines.push(`minimum-release-age-exclude=${exclude.join(",")}`);
|
|
208
|
+
}
|
|
209
|
+
return lines;
|
|
210
|
+
};
|
|
211
|
+
var parseNpmrc = (body) => {
|
|
212
|
+
const map = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
214
|
+
const line = rawLine.trim();
|
|
215
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const eq = line.indexOf("=");
|
|
219
|
+
if (eq < 0) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const key = line.slice(0, eq).trim();
|
|
223
|
+
const value = line.slice(eq + 1).trim();
|
|
224
|
+
if (key.length > 0) {
|
|
225
|
+
map.set(key, value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return map;
|
|
229
|
+
};
|
|
230
|
+
var mergeNpmrc = (existing, ourLines) => {
|
|
231
|
+
if (ourLines.length === 0) {
|
|
232
|
+
return existing ?? "";
|
|
233
|
+
}
|
|
234
|
+
const ourMap = /* @__PURE__ */ new Map();
|
|
235
|
+
for (const line of ourLines) {
|
|
236
|
+
const eq = line.indexOf("=");
|
|
237
|
+
if (eq < 0) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
ourMap.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());
|
|
241
|
+
}
|
|
242
|
+
const existingMap = parseNpmrc(existing ?? "");
|
|
243
|
+
for (const [key, value] of ourMap) {
|
|
244
|
+
existingMap.set(key, value);
|
|
245
|
+
}
|
|
246
|
+
const orderedKeys = [];
|
|
247
|
+
const seen = /* @__PURE__ */ new Set();
|
|
248
|
+
for (const rawLine of (existing ?? "").split(/\r?\n/)) {
|
|
249
|
+
const line = rawLine.trim();
|
|
250
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const eq = line.indexOf("=");
|
|
254
|
+
if (eq < 0) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const key = line.slice(0, eq).trim();
|
|
258
|
+
if (key.length > 0 && existingMap.has(key) && !seen.has(key)) {
|
|
259
|
+
orderedKeys.push(key);
|
|
260
|
+
seen.add(key);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const key of ourMap.keys()) {
|
|
264
|
+
if (!seen.has(key)) {
|
|
265
|
+
orderedKeys.push(key);
|
|
266
|
+
seen.add(key);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const body = orderedKeys.map((key) => `${key}=${existingMap.get(key)}`).join("\n");
|
|
270
|
+
return body.length === 0 ? "" : `${body}
|
|
271
|
+
`;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/lambda/pnpm-workspace-nodejs-function.ts
|
|
275
|
+
var mirrorPnpmWorkspacePolicy = (params) => {
|
|
276
|
+
const { entry, workspaceSearchFrom } = params;
|
|
277
|
+
if (!fs2.existsSync(entry)) {
|
|
278
|
+
return { skippedReason: "missing-entry" };
|
|
279
|
+
}
|
|
280
|
+
const entryDir = path2.dirname(path2.resolve(entry));
|
|
281
|
+
const searchFrom = workspaceSearchFrom ? path2.resolve(workspaceSearchFrom) : entryDir;
|
|
282
|
+
const workspaceFile = findPnpmWorkspaceFile(searchFrom);
|
|
283
|
+
if (!workspaceFile) {
|
|
284
|
+
return { skippedReason: "missing-workspace-file" };
|
|
285
|
+
}
|
|
286
|
+
const policy = readPnpmWorkspacePolicy(workspaceFile);
|
|
287
|
+
const lines = renderNpmrcLines(policy);
|
|
288
|
+
if (lines.length === 0) {
|
|
289
|
+
return { skippedReason: "empty-policy" };
|
|
290
|
+
}
|
|
291
|
+
const npmrcPath = path2.join(entryDir, ".npmrc");
|
|
292
|
+
const existing = fs2.existsSync(npmrcPath) ? fs2.readFileSync(npmrcPath, "utf8") : void 0;
|
|
293
|
+
const merged = mergeNpmrc(existing, lines);
|
|
294
|
+
if (merged === existing) {
|
|
295
|
+
return { npmrcPath, skippedReason: "no-change" };
|
|
296
|
+
}
|
|
297
|
+
fs2.writeFileSync(npmrcPath, merged);
|
|
298
|
+
return { npmrcPath };
|
|
299
|
+
};
|
|
300
|
+
var buildNpmrcCopyCommandHooks = (params) => {
|
|
301
|
+
const { npmrcPath, existingHooks } = params;
|
|
302
|
+
return {
|
|
303
|
+
beforeBundling(inputDir, outputDir) {
|
|
304
|
+
return existingHooks?.beforeBundling(inputDir, outputDir) ?? [];
|
|
305
|
+
},
|
|
306
|
+
beforeInstall(inputDir, outputDir) {
|
|
307
|
+
const callerCommands = existingHooks?.beforeInstall(inputDir, outputDir) ?? [];
|
|
308
|
+
const copyCommand = `cp ${npmrcPath} ${inputDir}/.npmrc`;
|
|
309
|
+
return [...callerCommands, copyCommand];
|
|
310
|
+
},
|
|
311
|
+
afterBundling(inputDir, outputDir) {
|
|
312
|
+
return existingHooks?.afterBundling(inputDir, outputDir) ?? [];
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
var PnpmWorkspaceNodejsFunction = class extends NodejsFunction {
|
|
317
|
+
constructor(scope, id, props) {
|
|
318
|
+
let mirrorResult;
|
|
319
|
+
if (!props.disablePnpmPolicyMirror && props.entry) {
|
|
320
|
+
mirrorResult = mirrorPnpmWorkspacePolicy({
|
|
321
|
+
entry: props.entry,
|
|
322
|
+
workspaceSearchFrom: props.workspaceSearchFrom
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const { disablePnpmPolicyMirror, workspaceSearchFrom, ...rest } = props;
|
|
326
|
+
void disablePnpmPolicyMirror;
|
|
327
|
+
void workspaceSearchFrom;
|
|
328
|
+
const npmrcPath = mirrorResult?.npmrcPath;
|
|
329
|
+
const shouldWireHook = npmrcPath !== void 0 && fs2.existsSync(npmrcPath);
|
|
330
|
+
const propsWithHook = shouldWireHook ? {
|
|
331
|
+
...rest,
|
|
332
|
+
bundling: {
|
|
333
|
+
...rest.bundling,
|
|
334
|
+
commandHooks: buildNpmrcCopyCommandHooks({
|
|
335
|
+
npmrcPath,
|
|
336
|
+
existingHooks: rest.bundling?.commandHooks
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
} : rest;
|
|
340
|
+
super(scope, id, propsWithHook);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
149
344
|
// src/s3/private-bucket.ts
|
|
150
345
|
import { RemovalPolicy } from "aws-cdk-lib";
|
|
151
346
|
import {
|
|
@@ -208,8 +403,8 @@ var StaticContent = class extends Construct {
|
|
|
208
403
|
};
|
|
209
404
|
|
|
210
405
|
// src/static-hosting/static-hosting.ts
|
|
211
|
-
import * as
|
|
212
|
-
import * as
|
|
406
|
+
import * as fs3 from "fs";
|
|
407
|
+
import * as path3 from "path";
|
|
213
408
|
import { Duration } from "aws-cdk-lib";
|
|
214
409
|
import {
|
|
215
410
|
Certificate,
|
|
@@ -230,7 +425,7 @@ import {
|
|
|
230
425
|
} from "aws-cdk-lib/aws-cloudfront";
|
|
231
426
|
import { S3BucketOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
232
427
|
import { Runtime } from "aws-cdk-lib/aws-lambda";
|
|
233
|
-
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
428
|
+
import { NodejsFunction as NodejsFunction2 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
234
429
|
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
235
430
|
import {
|
|
236
431
|
ARecord,
|
|
@@ -280,16 +475,16 @@ var StaticHosting = class extends Construct2 {
|
|
|
280
475
|
})
|
|
281
476
|
});
|
|
282
477
|
}
|
|
283
|
-
const handlerJs =
|
|
478
|
+
const handlerJs = path3.join(
|
|
284
479
|
__dirname,
|
|
285
480
|
"static-hosting.viewer-request-handler.js"
|
|
286
481
|
);
|
|
287
|
-
const handlerTs =
|
|
482
|
+
const handlerTs = path3.join(
|
|
288
483
|
__dirname,
|
|
289
484
|
"static-hosting.viewer-request-handler.ts"
|
|
290
485
|
);
|
|
291
|
-
const handlerEntry =
|
|
292
|
-
const handler = new
|
|
486
|
+
const handlerEntry = fs3.existsSync(handlerJs) ? handlerJs : handlerTs;
|
|
487
|
+
const handler = new NodejsFunction2(this, "viewer-request-handler", {
|
|
293
488
|
entry: handlerEntry,
|
|
294
489
|
handler: hostingMode === "static" ? "staticHandler" : "spaHandler",
|
|
295
490
|
memorySize: 128,
|
|
@@ -370,8 +565,16 @@ var StaticHosting = class extends Construct2 {
|
|
|
370
565
|
}
|
|
371
566
|
};
|
|
372
567
|
export {
|
|
568
|
+
PnpmWorkspaceNodejsFunction,
|
|
373
569
|
PrivateBucket,
|
|
374
570
|
StaticContent,
|
|
375
|
-
StaticHosting
|
|
571
|
+
StaticHosting,
|
|
572
|
+
buildNpmrcCopyCommandHooks,
|
|
573
|
+
findPnpmWorkspaceFile,
|
|
574
|
+
mergeNpmrc,
|
|
575
|
+
mirrorPnpmWorkspacePolicy,
|
|
576
|
+
parseNpmrc,
|
|
577
|
+
readPnpmWorkspacePolicy,
|
|
578
|
+
renderNpmrcLines
|
|
376
579
|
};
|
|
377
580
|
//# sourceMappingURL=index.mjs.map
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString - string to hash\n * @param trimLength - trim to this length (defaults to 999 chars)\n * @returns Hex-encoded sha256 digest, truncated to `trimLength` characters.\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString - string to truncate\n * @param maxLength - max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * `/stage.openhi.org/*` serves content to stage.openhi.org\n * `/feature-7.stage.openhi.org/*` serves content to feature-7.stage.openhi.org\n * `/pr-123.stage.openhi.org/*` serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.VITEST === \"true\";\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * `Lambda@Edge` handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html` so a\n * single-page app can serve its one root index and let the client-side\n * router handle the path.\n * - `static`: path-like URIs append `/index.html` (e.g. `/docs` →\n * `/docs/index.html`) so multi-page static sites can serve distinct\n * HTML per path.\n *\n * Multi-tenant domain-folder prepending runs after the rewrite in both\n * modes and is unaffected by this prop.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n hostingMode,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n hostingMode: \"spa\" as HostingMode,\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * `LAMBDA@EDGE` FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["findGitBranch","Bucket","StringParameter","Construct","Construct","StringParameter"]}
|
|
1
|
+
{"version":3,"sources":["../../utils/src/aws/aws-types.ts","../../utils/src/git/git-utils.ts","../../utils/src/string/string-utils.ts","../../utils/src/index.ts","../src/lambda/pnpm-workspace-nodejs-function.ts","../src/lambda/pnpm-workspace-parser.ts","../src/s3/private-bucket.ts","../src/static-hosting/static-content.ts","../src/static-hosting/static-hosting.ts"],"sourcesContent":["/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const AWS_STAGE_TYPE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type AwsStageType = (typeof AWS_STAGE_TYPE)[keyof typeof AWS_STAGE_TYPE];\n\n/**\n * Deployment target role: whether an (account, region) is the primary or\n * secondary deployment target (e.g. primary vs replica region).\n */\nexport const DEPLOYMENT_TARGET_ROLE = {\n /**\n * Account and region that represents the primary region for this service.\n * For example, the base DynamoDB Region for global tables.\n */\n PRIMARY: \"primary\",\n /**\n * Account and region that represents a secondary region for this service.\n * For example, a replica region for a global DynamoDB table.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Type for deployment target role values.\n */\nexport type DeploymentTargetRoleType =\n (typeof DEPLOYMENT_TARGET_ROLE)[keyof typeof DEPLOYMENT_TARGET_ROLE];\n\n/**\n * Environment types (primary/secondary).\n *\n * @deprecated Use {@link DEPLOYMENT_TARGET_ROLE} instead. This constant is maintained for backward compatibility.\n */\nexport const AWS_ENVIRONMENT_TYPE = DEPLOYMENT_TARGET_ROLE;\n\n/**\n * Type for environment type values.\n *\n * @deprecated Use {@link DeploymentTargetRoleType} instead. This type is maintained for backward compatibility.\n */\nexport type AwsEnvironmentType = DeploymentTargetRoleType;\n","import { execSync } from \"node:child_process\";\n\n/**\n * Returns the current full git branch name\n *\n * ie: feature/1234 returns feature/1234\n *\n */\nexport const findGitBranch = (): string => {\n return execSync(\"git rev-parse --abbrev-ref HEAD\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\");\n};\n\nexport const findGitRepoName = (): string => {\n /**\n * When running in github actions this will be populated.\n */\n if (process.env.GITHUB_REPOSITORY) {\n return process.env.GITHUB_REPOSITORY;\n }\n\n /**\n * locally, we need to extract the repo name from the git config.\n */\n const remote = execSync(\"git config --get remote.origin.url\")\n .toString(\"utf8\")\n .replace(/[\\n\\r\\s]+$/, \"\")\n .trim();\n\n const match = remote.match(/[:\\/]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n const repoName = match ? match[1] : \"error-repo-name\";\n\n return repoName;\n};\n","import * as crypto from \"node:crypto\";\n\n/**\n *\n * @param inString - string to hash\n * @param trimLength - trim to this length (defaults to 999 chars)\n * @returns Hex-encoded sha256 digest, truncated to `trimLength` characters.\n */\nexport const hashString = (inString: string, trimLength: number = 999) => {\n return crypto\n .createHash(\"sha256\")\n .update(inString)\n .digest(\"hex\")\n .substring(0, trimLength);\n};\n\n/**\n *\n * @param inputString - string to truncate\n * @param maxLength - max length of this string\n * @returns trimmed string\n */\nexport const trimStringLength = (inputString: string, maxLength: number) => {\n return inputString.length < maxLength\n ? inputString\n : inputString.substring(0, maxLength);\n};\n","export * from \"./aws/aws-types\";\nexport * from \"./git/git-utils\";\nexport * from \"./string/string-utils\";\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n ICommandHooks,\n NodejsFunction,\n NodejsFunctionProps,\n} from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n findPnpmWorkspaceFile,\n mergeNpmrc,\n readPnpmWorkspacePolicy,\n renderNpmrcLines,\n} from \"./pnpm-workspace-parser\";\n\n/**\n * Props for `PnpmWorkspaceNodejsFunction`. Extends\n * `NodejsFunctionProps` so the wrapper is a drop-in for any existing\n * `NodejsFunction` call site.\n */\nexport interface PnpmWorkspaceNodejsFunctionProps extends NodejsFunctionProps {\n /**\n * Directory to begin the upward search for `pnpm-workspace.yaml`.\n * Defaults to the directory of the resolved entry path.\n */\n readonly workspaceSearchFrom?: string;\n\n /**\n * Skip the policy-mirror step entirely. Useful for tests or when the\n * caller wants the pure `NodejsFunction` behavior.\n *\n * @default false\n */\n readonly disablePnpmPolicyMirror?: boolean;\n}\n\n/**\n * Result of `mirrorPnpmWorkspacePolicy` — describes what the policy\n * mirror did for one entry.\n */\nexport interface MirrorPnpmWorkspacePolicyResult {\n /** Resolved `.npmrc` path if a write happened, otherwise undefined. */\n readonly npmrcPath?: string;\n /** Reason the mirror was a no-op, if it was. */\n readonly skippedReason?:\n | \"missing-entry\"\n | \"missing-workspace-file\"\n | \"empty-policy\"\n | \"no-change\";\n}\n\n/**\n * Mirror the workspace pnpm policy into an `.npmrc` adjacent to the\n * Lambda entry. Pure side-effect helper used by\n * `PnpmWorkspaceNodejsFunction`; exported so consumers can drive it\n * from other call sites if desired.\n */\nexport const mirrorPnpmWorkspacePolicy = (params: {\n readonly entry: string;\n readonly workspaceSearchFrom?: string;\n}): MirrorPnpmWorkspacePolicyResult => {\n const { entry, workspaceSearchFrom } = params;\n if (!fs.existsSync(entry)) {\n return { skippedReason: \"missing-entry\" };\n }\n\n const entryDir = path.dirname(path.resolve(entry));\n const searchFrom = workspaceSearchFrom\n ? path.resolve(workspaceSearchFrom)\n : entryDir;\n\n const workspaceFile = findPnpmWorkspaceFile(searchFrom);\n if (!workspaceFile) {\n return { skippedReason: \"missing-workspace-file\" };\n }\n\n const policy = readPnpmWorkspacePolicy(workspaceFile);\n const lines = renderNpmrcLines(policy);\n if (lines.length === 0) {\n return { skippedReason: \"empty-policy\" };\n }\n\n const npmrcPath = path.join(entryDir, \".npmrc\");\n const existing = fs.existsSync(npmrcPath)\n ? fs.readFileSync(npmrcPath, \"utf8\")\n : undefined;\n const merged = mergeNpmrc(existing, lines);\n\n if (merged === existing) {\n return { npmrcPath, skippedReason: \"no-change\" };\n }\n\n fs.writeFileSync(npmrcPath, merged);\n return { npmrcPath };\n};\n\n/**\n * Build an `ICommandHooks` implementation that copies an\n * entry-adjacent `.npmrc` into the bundling input directory before\n * `pnpm install` runs. Composes with any caller-supplied hooks so\n * existing `beforeBundling` / `beforeInstall` / `afterBundling`\n * commands are preserved.\n *\n * Exported for unit testing; the construct wires this automatically.\n */\nexport const buildNpmrcCopyCommandHooks = (params: {\n readonly npmrcPath: string;\n readonly existingHooks?: ICommandHooks;\n}): ICommandHooks => {\n const { npmrcPath, existingHooks } = params;\n return {\n beforeBundling(inputDir: string, outputDir: string): string[] {\n return existingHooks?.beforeBundling(inputDir, outputDir) ?? [];\n },\n beforeInstall(inputDir: string, outputDir: string): string[] {\n const callerCommands =\n existingHooks?.beforeInstall(inputDir, outputDir) ?? [];\n const copyCommand = `cp ${npmrcPath} ${inputDir}/.npmrc`;\n return [...callerCommands, copyCommand];\n },\n afterBundling(inputDir: string, outputDir: string): string[] {\n return existingHooks?.afterBundling(inputDir, outputDir) ?? [];\n },\n };\n};\n\n/**\n * A `NodejsFunction` wrapper that mirrors the workspace's pnpm policy\n * (currently `minimumReleaseAge` and `minimumReleaseAgeExclude`) into\n * an `.npmrc` adjacent to the Lambda entry before bundling.\n *\n * CDK's bundler runs `pnpm install` in an isolated temp directory\n * outside the workspace, which means `pnpm-workspace.yaml` settings\n * do not apply there. CDK does **not** copy arbitrary entry-adjacent\n * files into the bundling input directory, so the construct also\n * wires a `bundling.commandHooks.beforeInstall` hook that copies the\n * rendered `.npmrc` into the bundling input directory immediately\n * before `pnpm install` runs. Caller-supplied `commandHooks` are\n * preserved — the copy command is appended to whatever the caller\n * already returns from `beforeInstall`.\n *\n * See the `pnpm-workspace-nodejs-function` documentation page for\n * full details on what gets mirrored, merge behaviour against an\n * existing `.npmrc`, and when to disable the mirror.\n */\nexport class PnpmWorkspaceNodejsFunction extends NodejsFunction {\n constructor(\n scope: Construct,\n id: string,\n props: PnpmWorkspaceNodejsFunctionProps,\n ) {\n let mirrorResult: MirrorPnpmWorkspacePolicyResult | undefined;\n if (!props.disablePnpmPolicyMirror && props.entry) {\n mirrorResult = mirrorPnpmWorkspacePolicy({\n entry: props.entry,\n workspaceSearchFrom: props.workspaceSearchFrom,\n });\n }\n\n const { disablePnpmPolicyMirror, workspaceSearchFrom, ...rest } = props;\n void disablePnpmPolicyMirror;\n void workspaceSearchFrom;\n\n // Wire the beforeInstall hook only when the mirror produced (or\n // confirmed) an .npmrc file on disk. Other skippedReason values\n // (`missing-entry`, `missing-workspace-file`, `empty-policy`) mean\n // there is nothing to copy; falling through to plain NodejsFunction\n // behavior is correct in those cases.\n const npmrcPath = mirrorResult?.npmrcPath;\n const shouldWireHook = npmrcPath !== undefined && fs.existsSync(npmrcPath);\n\n const propsWithHook: NodejsFunctionProps = shouldWireHook\n ? {\n ...rest,\n bundling: {\n ...rest.bundling,\n commandHooks: buildNpmrcCopyCommandHooks({\n npmrcPath,\n existingHooks: rest.bundling?.commandHooks,\n }),\n },\n }\n : rest;\n\n super(scope, id, propsWithHook);\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Subset of `pnpm-workspace.yaml` fields that map to `.npmrc` keys\n * pnpm honours when running install in a non-workspace directory.\n */\nexport interface PnpmWorkspacePolicy {\n /** `minimumReleaseAge` (minutes) — equivalent to `.npmrc` `minimum-release-age`. */\n readonly minimumReleaseAge?: number;\n /**\n * `minimumReleaseAgeExclude` — equivalent to `.npmrc`\n * `minimum-release-age-exclude` (comma-separated when serialised).\n */\n readonly minimumReleaseAgeExclude?: ReadonlyArray<string>;\n}\n\n/**\n * Walk up from `startDir` until a `pnpm-workspace.yaml` file is found\n * and return its absolute path. Returns `undefined` when the filesystem\n * root is reached without finding one.\n */\nexport const findPnpmWorkspaceFile = (startDir: string): string | undefined => {\n let current = path.resolve(startDir);\n while (true) {\n const candidate = path.join(current, \"pnpm-workspace.yaml\");\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n const parent = path.dirname(current);\n if (parent === current) {\n return undefined;\n }\n current = parent;\n }\n};\n\n/**\n * Read the policy subset (`minimumReleaseAge` and\n * `minimumReleaseAgeExclude`) from a `pnpm-workspace.yaml` file.\n *\n * Returns an empty object when the file is missing, unreadable, or\n * contains neither key. Throws when the file is present but YAML\n * parsing fails — the caller is expected to surface a clear error.\n */\nexport const readPnpmWorkspacePolicy = (\n workspaceFile: string,\n): PnpmWorkspacePolicy => {\n if (!fs.existsSync(workspaceFile)) {\n return {};\n }\n\n const raw = fs.readFileSync(workspaceFile, \"utf8\");\n const parsed = yaml.load(raw);\n\n if (!parsed || typeof parsed !== \"object\") {\n return {};\n }\n\n const obj = parsed as Record<string, unknown>;\n const policy: {\n -readonly [K in keyof PnpmWorkspacePolicy]: PnpmWorkspacePolicy[K];\n } = {};\n\n const age = obj.minimumReleaseAge;\n if (typeof age === \"number\" && Number.isFinite(age)) {\n policy.minimumReleaseAge = age;\n }\n\n const exclude = obj.minimumReleaseAgeExclude;\n if (Array.isArray(exclude)) {\n const cleaned = exclude.filter(\n (entry): entry is string => typeof entry === \"string\" && entry.length > 0,\n );\n if (cleaned.length > 0) {\n policy.minimumReleaseAgeExclude = cleaned;\n }\n }\n\n return policy;\n};\n\n/**\n * Render an `.npmrc` body that pnpm will honour when installing in\n * a directory outside the workspace tree. The keys emitted here are\n * the `.npmrc` equivalents of the `pnpm-workspace.yaml` fields in\n * {@link PnpmWorkspacePolicy} — pnpm reads `minimum-release-age` and\n * `minimum-release-age-exclude` from `.npmrc`, not from the workspace\n * file, when the install runs outside the workspace.\n *\n * Returns an empty string when the policy carries no relevant fields.\n */\nexport const renderNpmrcLines = (\n policy: PnpmWorkspacePolicy,\n): ReadonlyArray<string> => {\n const lines: Array<string> = [];\n\n if (typeof policy.minimumReleaseAge === \"number\") {\n lines.push(`minimum-release-age=${policy.minimumReleaseAge}`);\n }\n\n const exclude = policy.minimumReleaseAgeExclude ?? [];\n if (exclude.length > 0) {\n lines.push(`minimum-release-age-exclude=${exclude.join(\",\")}`);\n }\n\n return lines;\n};\n\n/**\n * Parse the body of an existing `.npmrc` file into a map of trimmed\n * key/value pairs. Lines that are blank or start with `#` are dropped.\n * Lines without an `=` are dropped.\n */\nexport const parseNpmrc = (body: string): Map<string, string> => {\n const map = new Map<string, string>();\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith(\"#\")) {\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n const key = line.slice(0, eq).trim();\n const value = line.slice(eq + 1).trim();\n if (key.length > 0) {\n map.set(key, value);\n }\n }\n return map;\n};\n\n/**\n * Merge a set of policy-derived `.npmrc` lines into an existing\n * `.npmrc` body. Our keys overwrite matching keys in the existing\n * body; all other keys are preserved. Returns the new `.npmrc` body\n * (always terminated by a trailing newline when non-empty).\n */\nexport const mergeNpmrc = (\n existing: string | undefined,\n ourLines: ReadonlyArray<string>,\n): string => {\n if (ourLines.length === 0) {\n return existing ?? \"\";\n }\n\n const ourMap = new Map<string, string>();\n for (const line of ourLines) {\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n ourMap.set(line.slice(0, eq).trim(), line.slice(eq + 1).trim());\n }\n\n const existingMap = parseNpmrc(existing ?? \"\");\n for (const [key, value] of ourMap) {\n existingMap.set(key, value);\n }\n\n const orderedKeys: Array<string> = [];\n const seen = new Set<string>();\n\n // Preserve the original line order for any pre-existing keys.\n for (const rawLine of (existing ?? \"\").split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith(\"#\")) {\n continue;\n }\n const eq = line.indexOf(\"=\");\n if (eq < 0) {\n continue;\n }\n const key = line.slice(0, eq).trim();\n if (key.length > 0 && existingMap.has(key) && !seen.has(key)) {\n orderedKeys.push(key);\n seen.add(key);\n }\n }\n\n // Append any keys we added that weren't already present.\n for (const key of ourMap.keys()) {\n if (!seen.has(key)) {\n orderedKeys.push(key);\n seen.add(key);\n }\n }\n\n const body = orderedKeys\n .map((key) => `${key}=${existingMap.get(key)}`)\n .join(\"\\n\");\n\n return body.length === 0 ? \"\" : `${body}\\n`;\n};\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n BlockPublicAccess,\n Bucket,\n BucketProps,\n ObjectOwnership,\n} from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\n\nexport interface PrivateBucketProps extends BucketProps {}\n\nexport class PrivateBucket extends Bucket {\n constructor(scope: Construct, id: string, props: PrivateBucketProps = {}) {\n const defaultProps = {\n removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n };\n\n const requiredProps = {\n publicReadAccess: false,\n blockPublicAccess: BlockPublicAccess.BLOCK_ALL,\n enforceSSL: true,\n objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,\n };\n\n super(scope, id, { ...defaultProps, ...props, ...requiredProps });\n }\n}\n","// eslint-disable-next-line import/no-extraneous-dependencies\nimport { findGitBranch } from \"@codedrifters/utils\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/domain/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket).\n *\n * `/stage.openhi.org/*` serves content to stage.openhi.org\n * `/feature-7.stage.openhi.org/*` serves content to feature-7.stage.openhi.org\n * `/pr-123.stage.openhi.org/*` serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\nexport interface StaticContentProps {\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n */\n readonly contentDestinationDirectory: string;\n\n /**\n * The sub domain prefix (ie: images)\n *\n * @default git branch name\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (ie: staging.codedrifters.com)\n */\n readonly fullDomain: string;\n}\n\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain,\n fullDomain,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n subDomain: findGitBranch(),\n ...props,\n };\n\n /***************************************************************************\n *\n * Import and build some values from Param Store during deployment.\n *\n **************************************************************************/\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n const bucketArn = StringParameter.valueForStringParameter(\n this,\n bucketArnParamName,\n );\n const bucket = Bucket.fromBucketArn(this, \"bucket\", bucketArn);\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. We need to have an empty source\n * for tests since it will change all the time and bork up the test\n * snapshots if we don't.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.VITEST === \"true\";\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { Duration, StackProps } from \"aws-cdk-lib\";\nimport {\n Certificate,\n CertificateValidation,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n CacheQueryStringBehavior,\n Distribution,\n LambdaEdgeEventType,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { PrivateBucket, PrivateBucketProps } from \"../s3/private-bucket\";\n\nexport interface StaticDomainProps {\n /**\n * The base domain (ie: codedrifters.com)\n */\n readonly baseDomain: string;\n\n /**\n * Hosted zone ID for the base domain.\n */\n readonly hostedZoneAttributes: HostedZoneAttributes;\n}\n\nexport interface StaticHostingProps extends StackProps {\n /**\n * Short description used in various places for traceability.\n */\n readonly description?: string;\n\n /**\n * Values used to connect a domain name to the cloudfront distribution. If not\n * supplied, cloudfront doesn't use a custom domain.\n */\n readonly staticDomainProps?: StaticDomainProps;\n\n /**\n * Parameter name to use when storing the static hosting bucket's ARN.\n * This is needed in other later steps when deploying hosted content to S3.\n */\n readonly bucketArnParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution Domain Name.\n */\n readonly distributionDomainParamName?: string;\n\n /**\n * Parameter name to use when storing the CloudFront Distribution ID.\n */\n readonly distributionIDParamName?: string;\n\n /**\n * Props to pass to the private S3 bucket.\n */\n readonly privateBucketProps?: PrivateBucketProps;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * `Lambda@Edge` handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html` so a\n * single-page app can serve its one root index and let the client-side\n * router handle the path.\n * - `static`: path-like URIs append `/index.html` (e.g. `/docs` →\n * `/docs/index.html`) so multi-page static sites can serve distinct\n * HTML per path.\n *\n * Multi-tenant domain-folder prepending runs after the rewrite in both\n * modes and is unaffected by this prop.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n}\n\nexport class StaticHosting extends Construct {\n /**\n * Full domain name used as basis for hosting.\n */\n public readonly fullDomain: string;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps = {}) {\n super(scope, id);\n\n /***************************************************************************\n *\n * Initial Setup\n *\n * Set some defaults, build domain information.\n *\n **************************************************************************/\n\n const {\n bucketArnParamName,\n distributionDomainParamName,\n distributionIDParamName,\n staticDomainProps,\n privateBucketProps,\n hostingMode,\n } = {\n bucketArnParamName: \"/STATIC_WEBSITE/BUCKET_ARN\",\n distributionDomainParamName: \"/STATIC_WEBSITE/DISTRIBUTION_DOMAIN\",\n distributionIDParamName: \"/STATIC_WEBSITE/DISTRIBUTION_ID\",\n hostingMode: \"spa\" as HostingMode,\n ...props,\n };\n\n const { baseDomain, hostedZoneAttributes } = staticDomainProps ?? {};\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * A bucket to store the files within.\n * Save ARN for later deploys.\n *\n **************************************************************************/\n\n const bucket = new PrivateBucket(\n this,\n \"static-hosting-bucket\",\n privateBucketProps,\n );\n\n /***************************************************************************\n *\n * DNS & Wildcard Certificate\n *\n * If a zone Id as passed in, find the hosted zone and create a wildcard\n * certificate for the domain.\n *\n **************************************************************************/\n\n let zone: IHostedZone | undefined;\n let certificate: Certificate | undefined;\n\n if (hostedZoneAttributes && baseDomain) {\n zone = HostedZone.fromHostedZoneAttributes(\n this,\n \"zone\",\n hostedZoneAttributes,\n );\n certificate = new Certificate(this, \"wildcard-certificate\", {\n domainName: `*.${baseDomain}`,\n subjectAlternativeNames: [baseDomain],\n validation: CertificateValidation.fromDnsMultiZone({\n [`*.${baseDomain}`]: zone,\n [baseDomain]: zone,\n }),\n });\n }\n\n /******************************************************************************\n *\n * `LAMBDA@EDGE` FUNCTION\n *\n * This handles rewriting the path from domain name.\n *\n *****************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n const handler = new NodejsFunction(this, \"viewer-request-handler\", {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_24_X,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n });\n\n /******************************************************************************\n *\n * CLOUDFRONT CONFIG\n *\n * Setup a CloudFront Distribution for the bucket.\n *\n *****************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cloudfront-policy\", {\n comment: \"Relatively conservative TTL policy.\",\n maxTtl: Duration.seconds(300),\n minTtl: Duration.seconds(0),\n defaultTtl: Duration.seconds(60),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n });\n\n const oac = new S3OriginAccessControl(this, \"MyOAC\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const distribution = new Distribution(this, \"cloudfront-distribution\", {\n comment: `Distribution for ${props.description ?? id}`,\n\n /**\n * Only if domain was supplied\n */\n ...(certificate && baseDomain\n ? {\n certificate,\n domainNames: [baseDomain, `*.${baseDomain}`],\n }\n : {}),\n\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: handler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n },\n ],\n },\n defaultRootObject: \"index.html\",\n });\n\n /**\n * We finally have enough information to set the full domain.\n */\n this.fullDomain =\n certificate && baseDomain ? baseDomain : distribution.domainName;\n\n /***************************************************************************\n *\n * DNS ENTRY\n *\n * Link cloudfront to both the root fulldomain and all possible subdomains.\n *\n **************************************************************************/\n\n if (zone) {\n new ARecord(this, \"root-dns-entry\", {\n zone,\n recordName: baseDomain ? baseDomain : \"\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n\n new ARecord(this, \"wc-dns-entry\", {\n zone,\n recordName: baseDomain ? `*.${baseDomain}` : \"*\",\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n\n /***************************************************************************\n *\n * EXPORTS\n *\n * Used by content uploader later.\n *\n **************************************************************************/\n\n new StringParameter(this, \"dist-domain\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionDomainParamName,\n stringValue: distribution.domainName,\n });\n\n new StringParameter(this, \"dist-id\", {\n description: `GENERATED DO NOT CHANGE - CloudFront Distribution Details (${props.description ?? id}).`,\n parameterName: distributionIDParamName,\n stringValue: distribution.distributionId,\n });\n\n new StringParameter(this, \"bucket-arn\", {\n description: `GENERATED DO NOT CHANGE - S3 Bucket ARN for (${props.description ?? id}).`,\n parameterName: bucketArnParamName,\n stringValue: bucket.bucketArn,\n });\n }\n}\n"],"mappings":"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","names":["findGitBranch","fs","path","Bucket","fs","path","NodejsFunction","StringParameter","Construct","Construct","NodejsFunction","StringParameter"]}
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@microsoft/api-extractor": "7.58.7",
|
|
14
|
+
"@types/js-yaml": "^4.0.9",
|
|
14
15
|
"@types/node": "25.8.0",
|
|
15
16
|
"@typescript-eslint/eslint-plugin": "^8",
|
|
16
17
|
"@typescript-eslint/parser": "^8",
|
|
@@ -40,7 +41,8 @@
|
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"@types/aws-lambda": "^8.10.161",
|
|
42
43
|
"change-case": "^4.0",
|
|
43
|
-
"esbuild": "^0.28.0"
|
|
44
|
+
"esbuild": "^0.28.0",
|
|
45
|
+
"js-yaml": "^4.1.0"
|
|
44
46
|
},
|
|
45
47
|
"devEngines": {
|
|
46
48
|
"packageManager": {
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
},
|
|
52
54
|
"main": "lib/index.js",
|
|
53
55
|
"license": "MIT",
|
|
54
|
-
"version": "0.0.
|
|
56
|
+
"version": "0.0.74",
|
|
55
57
|
"types": "lib/index.d.ts",
|
|
56
58
|
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
|
|
57
59
|
"scripts": {
|