@skuba-lib/api 2.2.0-add-cdk-NodejsFunction-20260613135656 → 2.2.0-hoist-changesets-20260614023310
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/cdk/index.cjs +8 -1
- package/lib/cdk/index.d.cts +4 -2
- package/lib/cdk/index.d.mts +4 -2
- package/lib/cdk/index.mjs +4 -2
- package/lib/index.cjs +7 -0
- package/lib/index.d.cts +5 -4
- package/lib/index.d.mts +5 -4
- package/lib/index.mjs +2 -1
- package/package.json +1 -27
- package/cdk/nodejsFunction/package.json +0 -6
- package/lib/cdk/bridges/rolldown.mjs +0 -49
- package/lib/cdk/nodejsFunction/index.cjs +0 -371
- package/lib/cdk/nodejsFunction/index.d.cts +0 -32
- package/lib/cdk/nodejsFunction/index.d.mts +0 -32
- package/lib/cdk/nodejsFunction/index.mjs +0 -365
package/lib/cdk/index.cjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
//#region src/cdk/
|
|
2
|
+
//#region src/cdk/index.ts
|
|
3
|
+
var cdk_exports = /* @__PURE__ */ require("../chunk-DakpK96I.cjs").__exportAll({ normaliseTemplate: () => normaliseTemplate });
|
|
3
4
|
const normaliseTemplateJsonString = (json) => json.replace(/"S3Key":"([0-9a-f]+)\.zip"/g, (_, hash) => `"S3Key":"${"x".repeat(hash.length)}.zip"`).replace(/workerCurrentVersion([0-9a-zA-Z]+)"/g, (_, hash) => `workerCurrentVersion${"x".repeat(hash.length)}"`).replaceAll(/"Value":"\d+\.\d+\.\d+-([^"]+)"/g, (_, hash) => `"Value": "x.x.x-${"x".repeat(hash.length)}"`).replaceAll(/"Value":"v\d+\.\d+\.\d+"/g, (_) => "\"Value\": \"vx.x.x\"").replace(/"DD_TAGS":"git.commit.sha:([0-9a-f]+),git.repository_url:([^\"]+)",/g, "").replaceAll(/(layer:Datadog-[^-]+-.+?:)\d+/g, (_, layer) => `${layer}x`);
|
|
4
5
|
const normaliseTemplate = (template) => JSON.parse(normaliseTemplateJsonString(JSON.stringify(template)));
|
|
5
6
|
//#endregion
|
|
7
|
+
Object.defineProperty(exports, "cdk_exports", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function() {
|
|
10
|
+
return cdk_exports;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
6
13
|
exports.normaliseTemplate = normaliseTemplate;
|
package/lib/cdk/index.d.cts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
declare namespace index_d_exports {
|
|
2
|
+
export { normaliseTemplate };
|
|
3
|
+
}
|
|
2
4
|
declare const normaliseTemplate: (template: object) => unknown;
|
|
3
5
|
//#endregion
|
|
4
|
-
export { normaliseTemplate };
|
|
6
|
+
export { normaliseTemplate, index_d_exports as t };
|
package/lib/cdk/index.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
declare namespace index_d_exports {
|
|
2
|
+
export { normaliseTemplate };
|
|
3
|
+
}
|
|
2
4
|
declare const normaliseTemplate: (template: object) => unknown;
|
|
3
5
|
//#endregion
|
|
4
|
-
export { normaliseTemplate };
|
|
6
|
+
export { normaliseTemplate, index_d_exports as t };
|
package/lib/cdk/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { t as __exportAll } from "../chunk-D7D4PA-g.mjs";
|
|
2
|
+
//#region src/cdk/index.ts
|
|
3
|
+
var cdk_exports = /* @__PURE__ */ __exportAll({ normaliseTemplate: () => normaliseTemplate });
|
|
2
4
|
const normaliseTemplateJsonString = (json) => json.replace(/"S3Key":"([0-9a-f]+)\.zip"/g, (_, hash) => `"S3Key":"${"x".repeat(hash.length)}.zip"`).replace(/workerCurrentVersion([0-9a-zA-Z]+)"/g, (_, hash) => `workerCurrentVersion${"x".repeat(hash.length)}"`).replaceAll(/"Value":"\d+\.\d+\.\d+-([^"]+)"/g, (_, hash) => `"Value": "x.x.x-${"x".repeat(hash.length)}"`).replaceAll(/"Value":"v\d+\.\d+\.\d+"/g, (_) => "\"Value\": \"vx.x.x\"").replace(/"DD_TAGS":"git.commit.sha:([0-9a-f]+),git.repository_url:([^\"]+)",/g, "").replaceAll(/(layer:Datadog-[^-]+-.+?:)\d+/g, (_, layer) => `${layer}x`);
|
|
3
5
|
const normaliseTemplate = (template) => JSON.parse(normaliseTemplateJsonString(JSON.stringify(template)));
|
|
4
6
|
//#endregion
|
|
5
|
-
export { normaliseTemplate };
|
|
7
|
+
export { normaliseTemplate, cdk_exports as t };
|
package/lib/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_buildkite = require("./buildkite-Dyd__LF3.cjs");
|
|
3
|
+
const require_cdk_index = require("./cdk/index.cjs");
|
|
3
4
|
const require_git = require("./git-CEnn1Qrs.cjs");
|
|
4
5
|
const require_github = require("./github-CzH1PaKH.cjs");
|
|
5
6
|
const require_net = require("./net-BGgnkCnN.cjs");
|
|
@@ -9,6 +10,12 @@ Object.defineProperty(exports, "Buildkite", {
|
|
|
9
10
|
return require_buildkite.buildkite_exports;
|
|
10
11
|
}
|
|
11
12
|
});
|
|
13
|
+
Object.defineProperty(exports, "Cdk", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function() {
|
|
16
|
+
return require_cdk_index.cdk_exports;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
12
19
|
Object.defineProperty(exports, "Git", {
|
|
13
20
|
enumerable: true,
|
|
14
21
|
get: function() {
|
package/lib/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as index_d_exports } from "./index-CNxgBJm_.cjs";
|
|
2
|
-
import { t as index_d_exports$1 } from "./index
|
|
3
|
-
import { t as index_d_exports$2 } from "./index-
|
|
4
|
-
import { t as index_d_exports$3 } from "./index-
|
|
5
|
-
|
|
2
|
+
import { t as index_d_exports$1 } from "./cdk/index.cjs";
|
|
3
|
+
import { t as index_d_exports$2 } from "./index-C9WMO_Y9.cjs";
|
|
4
|
+
import { t as index_d_exports$3 } from "./index-D4JUiy2k.cjs";
|
|
5
|
+
import { t as index_d_exports$4 } from "./index-Kyp8ZCwF.cjs";
|
|
6
|
+
export { index_d_exports as Buildkite, index_d_exports$1 as Cdk, index_d_exports$2 as Git, index_d_exports$3 as GitHub, index_d_exports$4 as Net };
|
package/lib/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as index_d_exports } from "./index-CNxgBJm_.mjs";
|
|
2
|
-
import { t as index_d_exports$1 } from "./index
|
|
3
|
-
import { t as index_d_exports$2 } from "./index-
|
|
4
|
-
import { t as index_d_exports$3 } from "./index-
|
|
5
|
-
|
|
2
|
+
import { t as index_d_exports$1 } from "./cdk/index.mjs";
|
|
3
|
+
import { t as index_d_exports$2 } from "./index-C9WMO_Y9.mjs";
|
|
4
|
+
import { t as index_d_exports$3 } from "./index-DbjgHTTm.mjs";
|
|
5
|
+
import { t as index_d_exports$4 } from "./index-Kyp8ZCwF.mjs";
|
|
6
|
+
export { index_d_exports as Buildkite, index_d_exports$1 as Cdk, index_d_exports$2 as Git, index_d_exports$3 as GitHub, index_d_exports$4 as Net };
|
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as buildkite_exports } from "./buildkite-CJ4a_6Cv.mjs";
|
|
2
|
+
import { t as cdk_exports } from "./cdk/index.mjs";
|
|
2
3
|
import { t as git_exports } from "./git-BSWQdkgU.mjs";
|
|
3
4
|
import { t as github_exports } from "./github-IYwiDs5D.mjs";
|
|
4
5
|
import { t as net_exports } from "./net-ZfhL_QSV.mjs";
|
|
5
|
-
export { buildkite_exports as Buildkite, git_exports as Git, github_exports as GitHub, net_exports as Net };
|
|
6
|
+
export { buildkite_exports as Buildkite, cdk_exports as Cdk, git_exports as Git, github_exports as GitHub, net_exports as Net };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skuba-lib/api",
|
|
3
|
-
"version": "2.2.0-
|
|
3
|
+
"version": "2.2.0-hoist-changesets-20260614023310",
|
|
4
4
|
"description": "Node.js development API for skuba",
|
|
5
5
|
"homepage": "https://github.com/seek-oss/skuba#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -26,10 +26,6 @@
|
|
|
26
26
|
"import": "./lib/cdk/index.mjs",
|
|
27
27
|
"require": "./lib/cdk/index.cjs"
|
|
28
28
|
},
|
|
29
|
-
"./cdk/nodejsFunction": {
|
|
30
|
-
"import": "./lib/cdk/nodejsFunction/index.mjs",
|
|
31
|
-
"require": "./lib/cdk/nodejsFunction/index.cjs"
|
|
32
|
-
},
|
|
33
29
|
"./git": {
|
|
34
30
|
"import": "./lib/git/index.mjs",
|
|
35
31
|
"require": "./lib/git/index.cjs"
|
|
@@ -66,30 +62,8 @@
|
|
|
66
62
|
"isomorphic-git": "^1.37.6",
|
|
67
63
|
"npm-run-path": "^6.0.0",
|
|
68
64
|
"npm-which": "^3.0.1",
|
|
69
|
-
"yaml": "^2.9.0",
|
|
70
65
|
"zod": "^4.3.5"
|
|
71
66
|
},
|
|
72
|
-
"devDependencies": {
|
|
73
|
-
"aws-cdk-lib": "^2.253.0",
|
|
74
|
-
"constructs": "^10.0.17",
|
|
75
|
-
"rolldown": "~1.0.0"
|
|
76
|
-
},
|
|
77
|
-
"peerDependencies": {
|
|
78
|
-
"aws-cdk-lib": ">=2.130.0",
|
|
79
|
-
"constructs": "^10.0.0",
|
|
80
|
-
"rolldown": ">=1.0.0"
|
|
81
|
-
},
|
|
82
|
-
"peerDependenciesMeta": {
|
|
83
|
-
"aws-cdk-lib": {
|
|
84
|
-
"optional": true
|
|
85
|
-
},
|
|
86
|
-
"constructs": {
|
|
87
|
-
"optional": true
|
|
88
|
-
},
|
|
89
|
-
"rolldown": {
|
|
90
|
-
"optional": true
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
67
|
"engines": {
|
|
94
68
|
"node": ">=22.14.0"
|
|
95
69
|
},
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { rolldown } from "rolldown";
|
|
5
|
-
//#region src/cdk/nodejsFunction/util.ts
|
|
6
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
-
const BUNDLE_META_FILENAME = ".lambda-bundle-meta";
|
|
8
|
-
const ESM_FORMATS = new Set([
|
|
9
|
-
"es",
|
|
10
|
-
"esm",
|
|
11
|
-
"module"
|
|
12
|
-
]);
|
|
13
|
-
const isEsmFormat = (format) => ESM_FORMATS.has(format ?? "");
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/cdk/nodejsFunction/bridges/rolldown.mts
|
|
16
|
-
const [configPath, entry, outputDir] = process.argv.slice(2, 5);
|
|
17
|
-
if (!configPath || !entry || !outputDir) throw new Error("Expected arguments: <configPath> <entry> <outputDir>");
|
|
18
|
-
const asRecord = (value) => isRecord(value) ? value : void 0;
|
|
19
|
-
const asString = (value) => typeof value === "string" ? value : void 0;
|
|
20
|
-
const userConfig = asRecord(asRecord(await import(pathToFileURL(configPath).href))?.default);
|
|
21
|
-
if (!userConfig) throw new Error(`Config file must export a default config object: ${configPath}`);
|
|
22
|
-
const rawOutput = userConfig.output;
|
|
23
|
-
if (Array.isArray(rawOutput) && rawOutput.length !== 1) throw new Error("`output` must be a single output object: NodejsFunction emits a single index.js/index.mjs handler.");
|
|
24
|
-
const baseRaw = asRecord(Array.isArray(rawOutput) ? rawOutput[0] : rawOutput) ?? {};
|
|
25
|
-
if (baseRaw.preserveModules) throw new Error("`output.preserveModules` is not supported: NodejsFunction emits a single index.js/index.mjs handler.");
|
|
26
|
-
if (baseRaw.entryFileNames !== void 0) throw new Error("`output.entryFileNames` is not supported: NodejsFunction names the handler index.js/index.mjs itself.");
|
|
27
|
-
if (userConfig.input !== void 0) throw new Error("`input` is not supported: NodejsFunction sets the bundle entry itself.");
|
|
28
|
-
const format = asString(baseRaw.format) ?? "es";
|
|
29
|
-
const { file: _file, ...baseRest } = baseRaw;
|
|
30
|
-
const { output: _output, ...userConfigRest } = userConfig;
|
|
31
|
-
const inputOptions = {
|
|
32
|
-
...userConfigRest,
|
|
33
|
-
input: entry
|
|
34
|
-
};
|
|
35
|
-
const outputOptions = {
|
|
36
|
-
...baseRest,
|
|
37
|
-
format,
|
|
38
|
-
dir: outputDir,
|
|
39
|
-
entryFileNames: isEsmFormat(format) ? "index.mjs" : "index.js"
|
|
40
|
-
};
|
|
41
|
-
const bundle = await rolldown(inputOptions);
|
|
42
|
-
try {
|
|
43
|
-
await bundle.write(outputOptions);
|
|
44
|
-
} finally {
|
|
45
|
-
await bundle.close();
|
|
46
|
-
}
|
|
47
|
-
writeFileSync(join(outputDir, BUNDLE_META_FILENAME), JSON.stringify({ format }));
|
|
48
|
-
//#endregion
|
|
49
|
-
export {};
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_chunk = require("../../chunk-DakpK96I.cjs");
|
|
3
|
-
let node_fs = require("node:fs");
|
|
4
|
-
node_fs = require_chunk.__toESM(node_fs, 1);
|
|
5
|
-
let node_path = require("node:path");
|
|
6
|
-
node_path = require_chunk.__toESM(node_path, 1);
|
|
7
|
-
let aws_cdk_lib_aws_lambda = require("aws-cdk-lib/aws-lambda");
|
|
8
|
-
aws_cdk_lib_aws_lambda = require_chunk.__toESM(aws_cdk_lib_aws_lambda, 1);
|
|
9
|
-
let node_child_process = require("node:child_process");
|
|
10
|
-
let aws_cdk_lib = require("aws-cdk-lib");
|
|
11
|
-
aws_cdk_lib = require_chunk.__toESM(aws_cdk_lib, 1);
|
|
12
|
-
let node_url = require("node:url");
|
|
13
|
-
let yaml = require("yaml");
|
|
14
|
-
let node_module = require("node:module");
|
|
15
|
-
//#region src/cdk/nodejsFunction/errors.ts
|
|
16
|
-
var ValidationError = class extends Error {
|
|
17
|
-
constructor(message) {
|
|
18
|
-
super(message);
|
|
19
|
-
this.name = "ValidationError";
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region src/cdk/nodejsFunction/bridge-path.ts
|
|
24
|
-
const findPackageRoot = (moduleUrl) => {
|
|
25
|
-
let dir = node_path.dirname((0, node_url.fileURLToPath)(moduleUrl));
|
|
26
|
-
for (;;) {
|
|
27
|
-
if (node_fs.existsSync(node_path.join(dir, "package.json"))) return dir;
|
|
28
|
-
const parent = node_path.dirname(dir);
|
|
29
|
-
if (parent === dir) throw new ValidationError("Unable to locate the package root to resolve the bundler bridge script.");
|
|
30
|
-
dir = parent;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const BRIDGE_OUTPUT_SEGMENTS = [
|
|
34
|
-
"lib",
|
|
35
|
-
"cdk",
|
|
36
|
-
"bridges",
|
|
37
|
-
"rolldown.mjs"
|
|
38
|
-
];
|
|
39
|
-
let bridgePath;
|
|
40
|
-
const resolveRolldownBridge = () => bridgePath ??= node_path.join(findPackageRoot(require("url").pathToFileURL(__filename).href), ...BRIDGE_OUTPUT_SEGMENTS);
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/cdk/nodejsFunction/util.ts
|
|
43
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
-
const BUNDLE_META_FILENAME = ".lambda-bundle-meta";
|
|
45
|
-
const ESM_FORMATS = new Set([
|
|
46
|
-
"es",
|
|
47
|
-
"esm",
|
|
48
|
-
"module"
|
|
49
|
-
]);
|
|
50
|
-
const isEsmFormat = (format) => ESM_FORMATS.has(format ?? "");
|
|
51
|
-
const parseJsonFile = (filePath) => {
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(node_fs.readFileSync(filePath, "utf-8"));
|
|
54
|
-
} catch (err) {
|
|
55
|
-
throw new ValidationError(`Failed to parse ${filePath} as JSON: ${err.message}`);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
const findUp = (name, directory = process.cwd()) => {
|
|
59
|
-
const absoluteDirectory = node_path.resolve(directory);
|
|
60
|
-
const file = node_path.join(absoluteDirectory, name);
|
|
61
|
-
if (node_fs.existsSync(file)) return file;
|
|
62
|
-
const { root } = node_path.parse(absoluteDirectory);
|
|
63
|
-
if (absoluteDirectory === root) return;
|
|
64
|
-
return findUp(name, node_path.dirname(absoluteDirectory));
|
|
65
|
-
};
|
|
66
|
-
const extractDependencies = (pkgPath, modules) => {
|
|
67
|
-
const result = {};
|
|
68
|
-
const base = (0, node_url.pathToFileURL)(pkgPath);
|
|
69
|
-
for (const mod of modules) {
|
|
70
|
-
let modPkgPath;
|
|
71
|
-
try {
|
|
72
|
-
modPkgPath = (0, node_module.findPackageJSON)(mod, base);
|
|
73
|
-
} catch {
|
|
74
|
-
modPkgPath = void 0;
|
|
75
|
-
}
|
|
76
|
-
const parsed = modPkgPath ? parseJsonFile(modPkgPath) : void 0;
|
|
77
|
-
const version = isRecord(parsed) && typeof parsed.version === "string" ? parsed.version.trim() : void 0;
|
|
78
|
-
if (!version) throw new ValidationError(`Cannot extract version for module '${mod}'. Check that it's referenced in your package.json or installed.`);
|
|
79
|
-
result[mod] = version;
|
|
80
|
-
}
|
|
81
|
-
return result;
|
|
82
|
-
};
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region src/cdk/nodejsFunction/package-manager.ts
|
|
85
|
-
const PNPM_LOCK = "pnpm-lock.yaml";
|
|
86
|
-
const PNPM_WORKSPACE_FILES = [
|
|
87
|
-
"pnpm-workspace.yaml",
|
|
88
|
-
".npmrc",
|
|
89
|
-
".pnpmfile.cjs",
|
|
90
|
-
".pnpmfile.mjs"
|
|
91
|
-
];
|
|
92
|
-
const PNPM_INSTALL_COMMAND = [
|
|
93
|
-
"pnpm",
|
|
94
|
-
"install",
|
|
95
|
-
"--config.node-linker=hoisted",
|
|
96
|
-
"--config.package-import-method=clone-or-copy",
|
|
97
|
-
"--no-frozen-lockfile"
|
|
98
|
-
];
|
|
99
|
-
const isInside = (parent, child) => {
|
|
100
|
-
const rel = node_path.relative(node_path.resolve(parent), node_path.resolve(child));
|
|
101
|
-
return rel === "" || !rel.startsWith("..") && !node_path.isAbsolute(rel);
|
|
102
|
-
};
|
|
103
|
-
const assertPnpm = (name, source, pkgPath) => {
|
|
104
|
-
if (name !== "pnpm") throw new ValidationError(`Unsupported package manager '${name}' declared in ${source} of ${pkgPath}. NodejsFunction only supports pnpm.`);
|
|
105
|
-
};
|
|
106
|
-
const readPackageManagerMeta = (projectRoot) => {
|
|
107
|
-
const pkgPath = node_path.join(projectRoot, "package.json");
|
|
108
|
-
const nearestPackageJson = node_fs.existsSync(pkgPath) ? pkgPath : void 0;
|
|
109
|
-
if (!nearestPackageJson) return {
|
|
110
|
-
nearestPackageJson,
|
|
111
|
-
packageManagerField: void 0
|
|
112
|
-
};
|
|
113
|
-
const parsed = parseJsonFile(pkgPath);
|
|
114
|
-
if (!isRecord(parsed)) return {
|
|
115
|
-
nearestPackageJson,
|
|
116
|
-
packageManagerField: void 0
|
|
117
|
-
};
|
|
118
|
-
if (isRecord(parsed.devEngines)) {
|
|
119
|
-
const devEnginesPm = parsed.devEngines.packageManager;
|
|
120
|
-
if (isRecord(devEnginesPm) && typeof devEnginesPm.name === "string") assertPnpm(devEnginesPm.name, "the `devEngines.packageManager` field", pkgPath);
|
|
121
|
-
}
|
|
122
|
-
if (typeof parsed.packageManager === "string") {
|
|
123
|
-
const m = /^([^@\s]+)@/.exec(parsed.packageManager);
|
|
124
|
-
if (m) {
|
|
125
|
-
assertPnpm(m[1], "the `packageManager` field", pkgPath);
|
|
126
|
-
return {
|
|
127
|
-
nearestPackageJson,
|
|
128
|
-
packageManagerField: parsed.packageManager
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
nearestPackageJson,
|
|
134
|
-
packageManagerField: void 0
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
const injectIgnoreScripts = (outputDir) => {
|
|
138
|
-
const target = node_path.join(outputDir, ".npmrc");
|
|
139
|
-
const existing = node_fs.existsSync(target) ? node_fs.readFileSync(target, "utf8") : "";
|
|
140
|
-
if (/^ignore-scripts\s*=/m.test(existing)) return;
|
|
141
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
142
|
-
node_fs.writeFileSync(target, `${existing}${sep}ignore-scripts=true\n`);
|
|
143
|
-
};
|
|
144
|
-
const copyPatchFile = (relativePath, projectRoot, outputDir, copiedFiles) => {
|
|
145
|
-
const src = node_path.join(projectRoot, relativePath);
|
|
146
|
-
const dest = node_path.join(outputDir, relativePath);
|
|
147
|
-
if (!isInside(projectRoot, src) || !isInside(outputDir, dest)) throw new ValidationError(`Refusing to copy pnpm patch '${relativePath}': it resolves outside the project root or output directory.`);
|
|
148
|
-
if (!node_fs.existsSync(src)) return;
|
|
149
|
-
const realSrc = node_fs.realpathSync(src);
|
|
150
|
-
if (!isInside(node_fs.realpathSync(projectRoot), realSrc)) throw new ValidationError(`Refusing to copy pnpm patch '${relativePath}': it resolves (via symlink) outside the project root.`);
|
|
151
|
-
node_fs.mkdirSync(node_path.dirname(dest), { recursive: true });
|
|
152
|
-
node_fs.copyFileSync(realSrc, dest);
|
|
153
|
-
copiedFiles.push(dest);
|
|
154
|
-
};
|
|
155
|
-
const filterPnpmWorkspaceYaml = (content, nodeModules, projectRoot, outputDir) => {
|
|
156
|
-
let doc;
|
|
157
|
-
try {
|
|
158
|
-
doc = (0, yaml.parse)(content);
|
|
159
|
-
} catch (err) {
|
|
160
|
-
throw new ValidationError(`Failed to parse pnpm-workspace.yaml: ${err.message}`);
|
|
161
|
-
}
|
|
162
|
-
if (!isRecord(doc) || !("patchedDependencies" in doc)) return {
|
|
163
|
-
content,
|
|
164
|
-
copiedFiles: []
|
|
165
|
-
};
|
|
166
|
-
const patched = doc.patchedDependencies;
|
|
167
|
-
const copiedFiles = [];
|
|
168
|
-
if (isRecord(patched)) for (const [key, value] of Object.entries(patched)) {
|
|
169
|
-
const pkgName = key.replace(/(?<=.)@[^@]+$/, "");
|
|
170
|
-
if (!nodeModules.includes(pkgName)) delete patched[key];
|
|
171
|
-
else if (typeof value === "string" && value) copyPatchFile(value, projectRoot, outputDir, copiedFiles);
|
|
172
|
-
}
|
|
173
|
-
if (!isRecord(patched) || Object.keys(patched).length === 0) delete doc.patchedDependencies;
|
|
174
|
-
return {
|
|
175
|
-
content: (0, yaml.stringify)(doc),
|
|
176
|
-
copiedFiles
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
const copyWorkspaceFiles = (projectRoot, outputDir, nodeModules = [], ignoreScripts = false) => {
|
|
180
|
-
const stagedFiles = [];
|
|
181
|
-
for (const file of PNPM_WORKSPACE_FILES) {
|
|
182
|
-
const src = node_path.join(projectRoot, file);
|
|
183
|
-
if (!node_fs.existsSync(src)) continue;
|
|
184
|
-
const dest = node_path.join(outputDir, file);
|
|
185
|
-
if (file === "pnpm-workspace.yaml") {
|
|
186
|
-
const filtered = filterPnpmWorkspaceYaml(node_fs.readFileSync(src, "utf8"), nodeModules, projectRoot, outputDir);
|
|
187
|
-
node_fs.writeFileSync(dest, filtered.content);
|
|
188
|
-
stagedFiles.push(...filtered.copiedFiles);
|
|
189
|
-
} else node_fs.copyFileSync(src, dest);
|
|
190
|
-
}
|
|
191
|
-
if (ignoreScripts) injectIgnoreScripts(outputDir);
|
|
192
|
-
return stagedFiles;
|
|
193
|
-
};
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region src/cdk/nodejsFunction/bundling.ts
|
|
196
|
-
const SPAWN_MAX_BUFFER = 256 * 1024 * 1024;
|
|
197
|
-
const describeExit = (result) => result.signal != null ? `killed by signal ${result.signal}` : `exited with status ${result.status}`;
|
|
198
|
-
const stderrTail = (stderr) => {
|
|
199
|
-
if (!stderr) return "";
|
|
200
|
-
const text = typeof stderr === "string" ? stderr : stderr.toString("utf-8");
|
|
201
|
-
if (!text) return "";
|
|
202
|
-
const tail = text.trimEnd().split("\n").slice(-20).join("\n");
|
|
203
|
-
return tail ? `\n\n${tail}` : "";
|
|
204
|
-
};
|
|
205
|
-
const checkSpawnResult = (result, prefix) => {
|
|
206
|
-
if (result.error) {
|
|
207
|
-
if (result.error.code === "ETIMEDOUT") throw new ValidationError(`${prefix} timed out.${stderrTail(result.stderr)}`);
|
|
208
|
-
throw result.error;
|
|
209
|
-
}
|
|
210
|
-
if (result.status !== 0) throw new ValidationError(`${prefix} ${describeExit(result)}.${stderrTail(result.stderr)}`);
|
|
211
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
212
|
-
};
|
|
213
|
-
const writeOutputPackageJson = (outputDir, fields) => {
|
|
214
|
-
const pkg = {};
|
|
215
|
-
if (fields.dependencies) pkg.dependencies = fields.dependencies;
|
|
216
|
-
if (fields.isEsm) pkg.type = "module";
|
|
217
|
-
if (fields.packageManager) pkg.packageManager = fields.packageManager;
|
|
218
|
-
if (Object.keys(pkg).length === 0) return;
|
|
219
|
-
node_fs.writeFileSync(node_path.join(outputDir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
220
|
-
};
|
|
221
|
-
const removeEmptyParentDirs = (startDir, outputDir) => {
|
|
222
|
-
let dir = startDir;
|
|
223
|
-
while (dir.startsWith(outputDir + node_path.sep)) {
|
|
224
|
-
try {
|
|
225
|
-
node_fs.rmdirSync(dir);
|
|
226
|
-
} catch {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
dir = node_path.dirname(dir);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
var Bundling = class Bundling {
|
|
233
|
-
props;
|
|
234
|
-
static bundle(props) {
|
|
235
|
-
return aws_cdk_lib_aws_lambda.Code.fromAsset(props.projectRoot, {
|
|
236
|
-
assetHash: props.assetHash,
|
|
237
|
-
assetHashType: props.assetHash ? aws_cdk_lib.AssetHashType.CUSTOM : aws_cdk_lib.AssetHashType.OUTPUT,
|
|
238
|
-
bundling: new Bundling(props)
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
image;
|
|
242
|
-
local;
|
|
243
|
-
constructor(props) {
|
|
244
|
-
this.props = props;
|
|
245
|
-
this.image = aws_cdk_lib.DockerImage.fromRegistry("dummy");
|
|
246
|
-
this.local = this.buildLocalBundling();
|
|
247
|
-
}
|
|
248
|
-
buildLocalBundling() {
|
|
249
|
-
return { tryBundle: (outputDir) => {
|
|
250
|
-
this.runHooks(this.props.commandHooks?.beforeBundling(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
251
|
-
this.runBundler(outputDir);
|
|
252
|
-
const isEsm = this.readBundleMeta(outputDir);
|
|
253
|
-
const { nodeModules } = this.props;
|
|
254
|
-
if (nodeModules?.length) this.installNodeModules(outputDir, isEsm, nodeModules);
|
|
255
|
-
else writeOutputPackageJson(outputDir, { isEsm });
|
|
256
|
-
this.runHooks(this.props.commandHooks?.afterBundling(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
257
|
-
return true;
|
|
258
|
-
} };
|
|
259
|
-
}
|
|
260
|
-
runHooks(commands, cwd) {
|
|
261
|
-
for (const cmd of commands) this.spawn(cmd, [], cwd, `Command hook '${cmd}'`, { shell: true });
|
|
262
|
-
}
|
|
263
|
-
runBundler(outputDir) {
|
|
264
|
-
this.spawn("node", [
|
|
265
|
-
resolveRolldownBridge(),
|
|
266
|
-
this.props.bundlerConfig,
|
|
267
|
-
this.props.entry,
|
|
268
|
-
outputDir
|
|
269
|
-
], this.props.projectRoot, "Bundler 'rolldown'");
|
|
270
|
-
}
|
|
271
|
-
readBundleMeta(outputDir) {
|
|
272
|
-
const metaPath = node_path.join(outputDir, BUNDLE_META_FILENAME);
|
|
273
|
-
if (!node_fs.existsSync(metaPath)) return false;
|
|
274
|
-
try {
|
|
275
|
-
const parsed = parseJsonFile(metaPath);
|
|
276
|
-
return isEsmFormat(isRecord(parsed) && typeof parsed.format === "string" ? parsed.format : void 0);
|
|
277
|
-
} finally {
|
|
278
|
-
node_fs.unlinkSync(metaPath);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
installNodeModules(outputDir, isEsm, nodeModules) {
|
|
282
|
-
const { nearestPackageJson, packageManagerField } = readPackageManagerMeta(this.props.projectRoot);
|
|
283
|
-
if (!nearestPackageJson) throw new ValidationError("Cannot find a package.json. Using nodeModules requires a package.json.");
|
|
284
|
-
this.runHooks(this.props.commandHooks?.beforeInstall(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
285
|
-
writeOutputPackageJson(outputDir, {
|
|
286
|
-
dependencies: extractDependencies(nearestPackageJson, nodeModules),
|
|
287
|
-
isEsm,
|
|
288
|
-
packageManager: packageManagerField
|
|
289
|
-
});
|
|
290
|
-
let stagedFiles = [];
|
|
291
|
-
try {
|
|
292
|
-
stagedFiles = copyWorkspaceFiles(this.props.projectRoot, outputDir, nodeModules, this.props.ignoreScripts);
|
|
293
|
-
if (node_fs.existsSync(this.props.depsLockFilePath)) node_fs.copyFileSync(this.props.depsLockFilePath, node_path.join(outputDir, PNPM_LOCK));
|
|
294
|
-
const [installBin, ...installArgs] = PNPM_INSTALL_COMMAND;
|
|
295
|
-
this.spawn(installBin, installArgs, outputDir, "Package manager 'pnpm' install");
|
|
296
|
-
} finally {
|
|
297
|
-
node_fs.rmSync(node_path.join(outputDir, PNPM_LOCK), { force: true });
|
|
298
|
-
for (const file of PNPM_WORKSPACE_FILES) node_fs.rmSync(node_path.join(outputDir, file), { force: true });
|
|
299
|
-
for (const file of stagedFiles) {
|
|
300
|
-
node_fs.rmSync(file, { force: true });
|
|
301
|
-
removeEmptyParentDirs(node_path.dirname(file), outputDir);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
spawn(bin, args, cwd, prefix, opts) {
|
|
306
|
-
checkSpawnResult((0, node_child_process.spawnSync)(bin, args, {
|
|
307
|
-
env: process.env,
|
|
308
|
-
stdio: [
|
|
309
|
-
"ignore",
|
|
310
|
-
"inherit",
|
|
311
|
-
"pipe"
|
|
312
|
-
],
|
|
313
|
-
cwd,
|
|
314
|
-
timeout: this.props.timeout,
|
|
315
|
-
encoding: "utf-8",
|
|
316
|
-
maxBuffer: SPAWN_MAX_BUFFER,
|
|
317
|
-
shell: opts?.shell
|
|
318
|
-
}), prefix);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
//#endregion
|
|
322
|
-
//#region src/cdk/nodejsFunction/function.ts
|
|
323
|
-
var NodejsFunction = class extends aws_cdk_lib_aws_lambda.Function {
|
|
324
|
-
constructor(scope, id, props) {
|
|
325
|
-
if (props.runtime && props.runtime.family !== aws_cdk_lib_aws_lambda.RuntimeFamily.NODEJS) throw new ValidationError("Only NODEJS runtimes are supported.");
|
|
326
|
-
const runtime = props.runtime ?? aws_cdk_lib_aws_lambda.Runtime.NODEJS_LATEST;
|
|
327
|
-
const entry = validateEntry(props.entry);
|
|
328
|
-
const depsLockFilePath = findLockFile(props.depsLockFilePath);
|
|
329
|
-
const projectRoot = node_path.resolve(props.projectRoot ?? node_path.dirname(depsLockFilePath));
|
|
330
|
-
const bundlerConfig = findBundlerConfig(props.bundling.bundlerConfig, projectRoot);
|
|
331
|
-
const handlerFn = resolveHandlerFn(props.handler);
|
|
332
|
-
const { entry: _entry, bundling, depsLockFilePath: _depsLockFilePath, projectRoot: _projectRoot, handler: _handler, runtime: _runtime, ...functionOptions } = props;
|
|
333
|
-
super(scope, id, {
|
|
334
|
-
...functionOptions,
|
|
335
|
-
runtime,
|
|
336
|
-
code: Bundling.bundle({
|
|
337
|
-
...bundling,
|
|
338
|
-
bundlerConfig,
|
|
339
|
-
entry,
|
|
340
|
-
runtime,
|
|
341
|
-
depsLockFilePath,
|
|
342
|
-
projectRoot
|
|
343
|
-
}),
|
|
344
|
-
handler: `index.${handlerFn}`
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
const resolveHandlerFn = (handler) => {
|
|
349
|
-
const handlerName = (handler ?? "handler").trim();
|
|
350
|
-
const handlerFn = handlerName.slice(handlerName.lastIndexOf(".") + 1);
|
|
351
|
-
if (!/^[A-Za-z_$][\w$]*$/.test(handlerFn)) throw new ValidationError(`Invalid handler '${handler}'. Expected an exported function name, optionally prefixed with a file part (e.g. 'handler' or 'index.handler').`);
|
|
352
|
-
return handlerFn;
|
|
353
|
-
};
|
|
354
|
-
const validateEntry = (entry) => {
|
|
355
|
-
const base = node_path.basename(entry);
|
|
356
|
-
if (/\.d\.(c|m)?ts$/i.test(base) || !/\.(js|ts|mjs|mts|cts|cjs)$/i.test(base)) throw new ValidationError("Only JavaScript or TypeScript entry files are supported.");
|
|
357
|
-
return node_path.resolve(entry);
|
|
358
|
-
};
|
|
359
|
-
const findBundlerConfig = (bundlerConfig, projectRoot) => node_path.isAbsolute(bundlerConfig) ? bundlerConfig : node_path.resolve(projectRoot, bundlerConfig);
|
|
360
|
-
const findLockFile = (depsLockFilePath) => {
|
|
361
|
-
if (depsLockFilePath) {
|
|
362
|
-
const resolved = node_path.resolve(depsLockFilePath);
|
|
363
|
-
if (!node_fs.existsSync(resolved)) throw new ValidationError(`Cannot find a pnpm-lock.yaml file at '${depsLockFilePath}'.`);
|
|
364
|
-
return resolved;
|
|
365
|
-
}
|
|
366
|
-
const lockFile = findUp(PNPM_LOCK);
|
|
367
|
-
if (!lockFile) throw new ValidationError("Cannot find a pnpm-lock.yaml file. Please specify it with `depsLockFilePath`.");
|
|
368
|
-
return lockFile;
|
|
369
|
-
};
|
|
370
|
-
//#endregion
|
|
371
|
-
exports.NodejsFunction = NodejsFunction;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
2
|
-
import { Construct } from "constructs";
|
|
3
|
-
|
|
4
|
-
//#region src/cdk/nodejsFunction/types.d.ts
|
|
5
|
-
interface ICommandHooks {
|
|
6
|
-
beforeBundling(inputDir: string, outputDir: string): string[];
|
|
7
|
-
afterBundling(inputDir: string, outputDir: string): string[];
|
|
8
|
-
beforeInstall(inputDir: string, outputDir: string): string[];
|
|
9
|
-
}
|
|
10
|
-
interface BundlingOptions {
|
|
11
|
-
bundlerConfig: string;
|
|
12
|
-
nodeModules?: string[];
|
|
13
|
-
commandHooks?: ICommandHooks;
|
|
14
|
-
assetHash?: string;
|
|
15
|
-
timeout?: number;
|
|
16
|
-
ignoreScripts?: boolean;
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/cdk/nodejsFunction/function.d.ts
|
|
20
|
-
interface NodejsFunctionProps extends lambda.FunctionOptions {
|
|
21
|
-
readonly entry: string;
|
|
22
|
-
readonly handler?: string;
|
|
23
|
-
readonly runtime?: lambda.Runtime;
|
|
24
|
-
readonly depsLockFilePath?: string;
|
|
25
|
-
readonly projectRoot?: string;
|
|
26
|
-
readonly bundling: BundlingOptions;
|
|
27
|
-
}
|
|
28
|
-
declare class NodejsFunction extends lambda.Function {
|
|
29
|
-
constructor(scope: Construct, id: string, props: NodejsFunctionProps);
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
export { type BundlingOptions, type ICommandHooks, NodejsFunction, type NodejsFunctionProps };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
2
|
-
import { Construct } from "constructs";
|
|
3
|
-
|
|
4
|
-
//#region src/cdk/nodejsFunction/types.d.ts
|
|
5
|
-
interface ICommandHooks {
|
|
6
|
-
beforeBundling(inputDir: string, outputDir: string): string[];
|
|
7
|
-
afterBundling(inputDir: string, outputDir: string): string[];
|
|
8
|
-
beforeInstall(inputDir: string, outputDir: string): string[];
|
|
9
|
-
}
|
|
10
|
-
interface BundlingOptions {
|
|
11
|
-
bundlerConfig: string;
|
|
12
|
-
nodeModules?: string[];
|
|
13
|
-
commandHooks?: ICommandHooks;
|
|
14
|
-
assetHash?: string;
|
|
15
|
-
timeout?: number;
|
|
16
|
-
ignoreScripts?: boolean;
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/cdk/nodejsFunction/function.d.ts
|
|
20
|
-
interface NodejsFunctionProps extends lambda.FunctionOptions {
|
|
21
|
-
readonly entry: string;
|
|
22
|
-
readonly handler?: string;
|
|
23
|
-
readonly runtime?: lambda.Runtime;
|
|
24
|
-
readonly depsLockFilePath?: string;
|
|
25
|
-
readonly projectRoot?: string;
|
|
26
|
-
readonly bundling: BundlingOptions;
|
|
27
|
-
}
|
|
28
|
-
declare class NodejsFunction extends lambda.Function {
|
|
29
|
-
constructor(scope: Construct, id: string, props: NodejsFunctionProps);
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
export { type BundlingOptions, type ICommandHooks, NodejsFunction, type NodejsFunctionProps };
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
import { findPackageJSON } from "node:module";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import * as cdk from "aws-cdk-lib";
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import { parse, stringify } from "yaml";
|
|
9
|
-
//#region src/cdk/nodejsFunction/errors.ts
|
|
10
|
-
var ValidationError = class extends Error {
|
|
11
|
-
constructor(message) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = "ValidationError";
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/cdk/nodejsFunction/bridge-path.ts
|
|
18
|
-
const findPackageRoot = (moduleUrl) => {
|
|
19
|
-
let dir = path.dirname(fileURLToPath(moduleUrl));
|
|
20
|
-
for (;;) {
|
|
21
|
-
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
|
|
22
|
-
const parent = path.dirname(dir);
|
|
23
|
-
if (parent === dir) throw new ValidationError("Unable to locate the package root to resolve the bundler bridge script.");
|
|
24
|
-
dir = parent;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
const BRIDGE_OUTPUT_SEGMENTS = [
|
|
28
|
-
"lib",
|
|
29
|
-
"cdk",
|
|
30
|
-
"bridges",
|
|
31
|
-
"rolldown.mjs"
|
|
32
|
-
];
|
|
33
|
-
let bridgePath;
|
|
34
|
-
const resolveRolldownBridge = () => bridgePath ??= path.join(findPackageRoot(import.meta.url), ...BRIDGE_OUTPUT_SEGMENTS);
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/cdk/nodejsFunction/util.ts
|
|
37
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38
|
-
const BUNDLE_META_FILENAME = ".lambda-bundle-meta";
|
|
39
|
-
const ESM_FORMATS = new Set([
|
|
40
|
-
"es",
|
|
41
|
-
"esm",
|
|
42
|
-
"module"
|
|
43
|
-
]);
|
|
44
|
-
const isEsmFormat = (format) => ESM_FORMATS.has(format ?? "");
|
|
45
|
-
const parseJsonFile = (filePath) => {
|
|
46
|
-
try {
|
|
47
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
48
|
-
} catch (err) {
|
|
49
|
-
throw new ValidationError(`Failed to parse ${filePath} as JSON: ${err.message}`);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
const findUp = (name, directory = process.cwd()) => {
|
|
53
|
-
const absoluteDirectory = path.resolve(directory);
|
|
54
|
-
const file = path.join(absoluteDirectory, name);
|
|
55
|
-
if (fs.existsSync(file)) return file;
|
|
56
|
-
const { root } = path.parse(absoluteDirectory);
|
|
57
|
-
if (absoluteDirectory === root) return;
|
|
58
|
-
return findUp(name, path.dirname(absoluteDirectory));
|
|
59
|
-
};
|
|
60
|
-
const extractDependencies = (pkgPath, modules) => {
|
|
61
|
-
const result = {};
|
|
62
|
-
const base = pathToFileURL(pkgPath);
|
|
63
|
-
for (const mod of modules) {
|
|
64
|
-
let modPkgPath;
|
|
65
|
-
try {
|
|
66
|
-
modPkgPath = findPackageJSON(mod, base);
|
|
67
|
-
} catch {
|
|
68
|
-
modPkgPath = void 0;
|
|
69
|
-
}
|
|
70
|
-
const parsed = modPkgPath ? parseJsonFile(modPkgPath) : void 0;
|
|
71
|
-
const version = isRecord(parsed) && typeof parsed.version === "string" ? parsed.version.trim() : void 0;
|
|
72
|
-
if (!version) throw new ValidationError(`Cannot extract version for module '${mod}'. Check that it's referenced in your package.json or installed.`);
|
|
73
|
-
result[mod] = version;
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
76
|
-
};
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/cdk/nodejsFunction/package-manager.ts
|
|
79
|
-
const PNPM_LOCK = "pnpm-lock.yaml";
|
|
80
|
-
const PNPM_WORKSPACE_FILES = [
|
|
81
|
-
"pnpm-workspace.yaml",
|
|
82
|
-
".npmrc",
|
|
83
|
-
".pnpmfile.cjs",
|
|
84
|
-
".pnpmfile.mjs"
|
|
85
|
-
];
|
|
86
|
-
const PNPM_INSTALL_COMMAND = [
|
|
87
|
-
"pnpm",
|
|
88
|
-
"install",
|
|
89
|
-
"--config.node-linker=hoisted",
|
|
90
|
-
"--config.package-import-method=clone-or-copy",
|
|
91
|
-
"--no-frozen-lockfile"
|
|
92
|
-
];
|
|
93
|
-
const isInside = (parent, child) => {
|
|
94
|
-
const rel = path.relative(path.resolve(parent), path.resolve(child));
|
|
95
|
-
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
96
|
-
};
|
|
97
|
-
const assertPnpm = (name, source, pkgPath) => {
|
|
98
|
-
if (name !== "pnpm") throw new ValidationError(`Unsupported package manager '${name}' declared in ${source} of ${pkgPath}. NodejsFunction only supports pnpm.`);
|
|
99
|
-
};
|
|
100
|
-
const readPackageManagerMeta = (projectRoot) => {
|
|
101
|
-
const pkgPath = path.join(projectRoot, "package.json");
|
|
102
|
-
const nearestPackageJson = fs.existsSync(pkgPath) ? pkgPath : void 0;
|
|
103
|
-
if (!nearestPackageJson) return {
|
|
104
|
-
nearestPackageJson,
|
|
105
|
-
packageManagerField: void 0
|
|
106
|
-
};
|
|
107
|
-
const parsed = parseJsonFile(pkgPath);
|
|
108
|
-
if (!isRecord(parsed)) return {
|
|
109
|
-
nearestPackageJson,
|
|
110
|
-
packageManagerField: void 0
|
|
111
|
-
};
|
|
112
|
-
if (isRecord(parsed.devEngines)) {
|
|
113
|
-
const devEnginesPm = parsed.devEngines.packageManager;
|
|
114
|
-
if (isRecord(devEnginesPm) && typeof devEnginesPm.name === "string") assertPnpm(devEnginesPm.name, "the `devEngines.packageManager` field", pkgPath);
|
|
115
|
-
}
|
|
116
|
-
if (typeof parsed.packageManager === "string") {
|
|
117
|
-
const m = /^([^@\s]+)@/.exec(parsed.packageManager);
|
|
118
|
-
if (m) {
|
|
119
|
-
assertPnpm(m[1], "the `packageManager` field", pkgPath);
|
|
120
|
-
return {
|
|
121
|
-
nearestPackageJson,
|
|
122
|
-
packageManagerField: parsed.packageManager
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
nearestPackageJson,
|
|
128
|
-
packageManagerField: void 0
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
const injectIgnoreScripts = (outputDir) => {
|
|
132
|
-
const target = path.join(outputDir, ".npmrc");
|
|
133
|
-
const existing = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : "";
|
|
134
|
-
if (/^ignore-scripts\s*=/m.test(existing)) return;
|
|
135
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
136
|
-
fs.writeFileSync(target, `${existing}${sep}ignore-scripts=true\n`);
|
|
137
|
-
};
|
|
138
|
-
const copyPatchFile = (relativePath, projectRoot, outputDir, copiedFiles) => {
|
|
139
|
-
const src = path.join(projectRoot, relativePath);
|
|
140
|
-
const dest = path.join(outputDir, relativePath);
|
|
141
|
-
if (!isInside(projectRoot, src) || !isInside(outputDir, dest)) throw new ValidationError(`Refusing to copy pnpm patch '${relativePath}': it resolves outside the project root or output directory.`);
|
|
142
|
-
if (!fs.existsSync(src)) return;
|
|
143
|
-
const realSrc = fs.realpathSync(src);
|
|
144
|
-
if (!isInside(fs.realpathSync(projectRoot), realSrc)) throw new ValidationError(`Refusing to copy pnpm patch '${relativePath}': it resolves (via symlink) outside the project root.`);
|
|
145
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
146
|
-
fs.copyFileSync(realSrc, dest);
|
|
147
|
-
copiedFiles.push(dest);
|
|
148
|
-
};
|
|
149
|
-
const filterPnpmWorkspaceYaml = (content, nodeModules, projectRoot, outputDir) => {
|
|
150
|
-
let doc;
|
|
151
|
-
try {
|
|
152
|
-
doc = parse(content);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
throw new ValidationError(`Failed to parse pnpm-workspace.yaml: ${err.message}`);
|
|
155
|
-
}
|
|
156
|
-
if (!isRecord(doc) || !("patchedDependencies" in doc)) return {
|
|
157
|
-
content,
|
|
158
|
-
copiedFiles: []
|
|
159
|
-
};
|
|
160
|
-
const patched = doc.patchedDependencies;
|
|
161
|
-
const copiedFiles = [];
|
|
162
|
-
if (isRecord(patched)) for (const [key, value] of Object.entries(patched)) {
|
|
163
|
-
const pkgName = key.replace(/(?<=.)@[^@]+$/, "");
|
|
164
|
-
if (!nodeModules.includes(pkgName)) delete patched[key];
|
|
165
|
-
else if (typeof value === "string" && value) copyPatchFile(value, projectRoot, outputDir, copiedFiles);
|
|
166
|
-
}
|
|
167
|
-
if (!isRecord(patched) || Object.keys(patched).length === 0) delete doc.patchedDependencies;
|
|
168
|
-
return {
|
|
169
|
-
content: stringify(doc),
|
|
170
|
-
copiedFiles
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
const copyWorkspaceFiles = (projectRoot, outputDir, nodeModules = [], ignoreScripts = false) => {
|
|
174
|
-
const stagedFiles = [];
|
|
175
|
-
for (const file of PNPM_WORKSPACE_FILES) {
|
|
176
|
-
const src = path.join(projectRoot, file);
|
|
177
|
-
if (!fs.existsSync(src)) continue;
|
|
178
|
-
const dest = path.join(outputDir, file);
|
|
179
|
-
if (file === "pnpm-workspace.yaml") {
|
|
180
|
-
const filtered = filterPnpmWorkspaceYaml(fs.readFileSync(src, "utf8"), nodeModules, projectRoot, outputDir);
|
|
181
|
-
fs.writeFileSync(dest, filtered.content);
|
|
182
|
-
stagedFiles.push(...filtered.copiedFiles);
|
|
183
|
-
} else fs.copyFileSync(src, dest);
|
|
184
|
-
}
|
|
185
|
-
if (ignoreScripts) injectIgnoreScripts(outputDir);
|
|
186
|
-
return stagedFiles;
|
|
187
|
-
};
|
|
188
|
-
//#endregion
|
|
189
|
-
//#region src/cdk/nodejsFunction/bundling.ts
|
|
190
|
-
const SPAWN_MAX_BUFFER = 256 * 1024 * 1024;
|
|
191
|
-
const describeExit = (result) => result.signal != null ? `killed by signal ${result.signal}` : `exited with status ${result.status}`;
|
|
192
|
-
const stderrTail = (stderr) => {
|
|
193
|
-
if (!stderr) return "";
|
|
194
|
-
const text = typeof stderr === "string" ? stderr : stderr.toString("utf-8");
|
|
195
|
-
if (!text) return "";
|
|
196
|
-
const tail = text.trimEnd().split("\n").slice(-20).join("\n");
|
|
197
|
-
return tail ? `\n\n${tail}` : "";
|
|
198
|
-
};
|
|
199
|
-
const checkSpawnResult = (result, prefix) => {
|
|
200
|
-
if (result.error) {
|
|
201
|
-
if (result.error.code === "ETIMEDOUT") throw new ValidationError(`${prefix} timed out.${stderrTail(result.stderr)}`);
|
|
202
|
-
throw result.error;
|
|
203
|
-
}
|
|
204
|
-
if (result.status !== 0) throw new ValidationError(`${prefix} ${describeExit(result)}.${stderrTail(result.stderr)}`);
|
|
205
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
206
|
-
};
|
|
207
|
-
const writeOutputPackageJson = (outputDir, fields) => {
|
|
208
|
-
const pkg = {};
|
|
209
|
-
if (fields.dependencies) pkg.dependencies = fields.dependencies;
|
|
210
|
-
if (fields.isEsm) pkg.type = "module";
|
|
211
|
-
if (fields.packageManager) pkg.packageManager = fields.packageManager;
|
|
212
|
-
if (Object.keys(pkg).length === 0) return;
|
|
213
|
-
fs.writeFileSync(path.join(outputDir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
214
|
-
};
|
|
215
|
-
const removeEmptyParentDirs = (startDir, outputDir) => {
|
|
216
|
-
let dir = startDir;
|
|
217
|
-
while (dir.startsWith(outputDir + path.sep)) {
|
|
218
|
-
try {
|
|
219
|
-
fs.rmdirSync(dir);
|
|
220
|
-
} catch {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
dir = path.dirname(dir);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
var Bundling = class Bundling {
|
|
227
|
-
props;
|
|
228
|
-
static bundle(props) {
|
|
229
|
-
return lambda.Code.fromAsset(props.projectRoot, {
|
|
230
|
-
assetHash: props.assetHash,
|
|
231
|
-
assetHashType: props.assetHash ? cdk.AssetHashType.CUSTOM : cdk.AssetHashType.OUTPUT,
|
|
232
|
-
bundling: new Bundling(props)
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
image;
|
|
236
|
-
local;
|
|
237
|
-
constructor(props) {
|
|
238
|
-
this.props = props;
|
|
239
|
-
this.image = cdk.DockerImage.fromRegistry("dummy");
|
|
240
|
-
this.local = this.buildLocalBundling();
|
|
241
|
-
}
|
|
242
|
-
buildLocalBundling() {
|
|
243
|
-
return { tryBundle: (outputDir) => {
|
|
244
|
-
this.runHooks(this.props.commandHooks?.beforeBundling(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
245
|
-
this.runBundler(outputDir);
|
|
246
|
-
const isEsm = this.readBundleMeta(outputDir);
|
|
247
|
-
const { nodeModules } = this.props;
|
|
248
|
-
if (nodeModules?.length) this.installNodeModules(outputDir, isEsm, nodeModules);
|
|
249
|
-
else writeOutputPackageJson(outputDir, { isEsm });
|
|
250
|
-
this.runHooks(this.props.commandHooks?.afterBundling(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
251
|
-
return true;
|
|
252
|
-
} };
|
|
253
|
-
}
|
|
254
|
-
runHooks(commands, cwd) {
|
|
255
|
-
for (const cmd of commands) this.spawn(cmd, [], cwd, `Command hook '${cmd}'`, { shell: true });
|
|
256
|
-
}
|
|
257
|
-
runBundler(outputDir) {
|
|
258
|
-
this.spawn("node", [
|
|
259
|
-
resolveRolldownBridge(),
|
|
260
|
-
this.props.bundlerConfig,
|
|
261
|
-
this.props.entry,
|
|
262
|
-
outputDir
|
|
263
|
-
], this.props.projectRoot, "Bundler 'rolldown'");
|
|
264
|
-
}
|
|
265
|
-
readBundleMeta(outputDir) {
|
|
266
|
-
const metaPath = path.join(outputDir, BUNDLE_META_FILENAME);
|
|
267
|
-
if (!fs.existsSync(metaPath)) return false;
|
|
268
|
-
try {
|
|
269
|
-
const parsed = parseJsonFile(metaPath);
|
|
270
|
-
return isEsmFormat(isRecord(parsed) && typeof parsed.format === "string" ? parsed.format : void 0);
|
|
271
|
-
} finally {
|
|
272
|
-
fs.unlinkSync(metaPath);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
installNodeModules(outputDir, isEsm, nodeModules) {
|
|
276
|
-
const { nearestPackageJson, packageManagerField } = readPackageManagerMeta(this.props.projectRoot);
|
|
277
|
-
if (!nearestPackageJson) throw new ValidationError("Cannot find a package.json. Using nodeModules requires a package.json.");
|
|
278
|
-
this.runHooks(this.props.commandHooks?.beforeInstall(this.props.projectRoot, outputDir) ?? [], this.props.projectRoot);
|
|
279
|
-
writeOutputPackageJson(outputDir, {
|
|
280
|
-
dependencies: extractDependencies(nearestPackageJson, nodeModules),
|
|
281
|
-
isEsm,
|
|
282
|
-
packageManager: packageManagerField
|
|
283
|
-
});
|
|
284
|
-
let stagedFiles = [];
|
|
285
|
-
try {
|
|
286
|
-
stagedFiles = copyWorkspaceFiles(this.props.projectRoot, outputDir, nodeModules, this.props.ignoreScripts);
|
|
287
|
-
if (fs.existsSync(this.props.depsLockFilePath)) fs.copyFileSync(this.props.depsLockFilePath, path.join(outputDir, PNPM_LOCK));
|
|
288
|
-
const [installBin, ...installArgs] = PNPM_INSTALL_COMMAND;
|
|
289
|
-
this.spawn(installBin, installArgs, outputDir, "Package manager 'pnpm' install");
|
|
290
|
-
} finally {
|
|
291
|
-
fs.rmSync(path.join(outputDir, PNPM_LOCK), { force: true });
|
|
292
|
-
for (const file of PNPM_WORKSPACE_FILES) fs.rmSync(path.join(outputDir, file), { force: true });
|
|
293
|
-
for (const file of stagedFiles) {
|
|
294
|
-
fs.rmSync(file, { force: true });
|
|
295
|
-
removeEmptyParentDirs(path.dirname(file), outputDir);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
spawn(bin, args, cwd, prefix, opts) {
|
|
300
|
-
checkSpawnResult(spawnSync(bin, args, {
|
|
301
|
-
env: process.env,
|
|
302
|
-
stdio: [
|
|
303
|
-
"ignore",
|
|
304
|
-
"inherit",
|
|
305
|
-
"pipe"
|
|
306
|
-
],
|
|
307
|
-
cwd,
|
|
308
|
-
timeout: this.props.timeout,
|
|
309
|
-
encoding: "utf-8",
|
|
310
|
-
maxBuffer: SPAWN_MAX_BUFFER,
|
|
311
|
-
shell: opts?.shell
|
|
312
|
-
}), prefix);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
//#endregion
|
|
316
|
-
//#region src/cdk/nodejsFunction/function.ts
|
|
317
|
-
var NodejsFunction = class extends lambda.Function {
|
|
318
|
-
constructor(scope, id, props) {
|
|
319
|
-
if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) throw new ValidationError("Only NODEJS runtimes are supported.");
|
|
320
|
-
const runtime = props.runtime ?? lambda.Runtime.NODEJS_LATEST;
|
|
321
|
-
const entry = validateEntry(props.entry);
|
|
322
|
-
const depsLockFilePath = findLockFile(props.depsLockFilePath);
|
|
323
|
-
const projectRoot = path.resolve(props.projectRoot ?? path.dirname(depsLockFilePath));
|
|
324
|
-
const bundlerConfig = findBundlerConfig(props.bundling.bundlerConfig, projectRoot);
|
|
325
|
-
const handlerFn = resolveHandlerFn(props.handler);
|
|
326
|
-
const { entry: _entry, bundling, depsLockFilePath: _depsLockFilePath, projectRoot: _projectRoot, handler: _handler, runtime: _runtime, ...functionOptions } = props;
|
|
327
|
-
super(scope, id, {
|
|
328
|
-
...functionOptions,
|
|
329
|
-
runtime,
|
|
330
|
-
code: Bundling.bundle({
|
|
331
|
-
...bundling,
|
|
332
|
-
bundlerConfig,
|
|
333
|
-
entry,
|
|
334
|
-
runtime,
|
|
335
|
-
depsLockFilePath,
|
|
336
|
-
projectRoot
|
|
337
|
-
}),
|
|
338
|
-
handler: `index.${handlerFn}`
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
const resolveHandlerFn = (handler) => {
|
|
343
|
-
const handlerName = (handler ?? "handler").trim();
|
|
344
|
-
const handlerFn = handlerName.slice(handlerName.lastIndexOf(".") + 1);
|
|
345
|
-
if (!/^[A-Za-z_$][\w$]*$/.test(handlerFn)) throw new ValidationError(`Invalid handler '${handler}'. Expected an exported function name, optionally prefixed with a file part (e.g. 'handler' or 'index.handler').`);
|
|
346
|
-
return handlerFn;
|
|
347
|
-
};
|
|
348
|
-
const validateEntry = (entry) => {
|
|
349
|
-
const base = path.basename(entry);
|
|
350
|
-
if (/\.d\.(c|m)?ts$/i.test(base) || !/\.(js|ts|mjs|mts|cts|cjs)$/i.test(base)) throw new ValidationError("Only JavaScript or TypeScript entry files are supported.");
|
|
351
|
-
return path.resolve(entry);
|
|
352
|
-
};
|
|
353
|
-
const findBundlerConfig = (bundlerConfig, projectRoot) => path.isAbsolute(bundlerConfig) ? bundlerConfig : path.resolve(projectRoot, bundlerConfig);
|
|
354
|
-
const findLockFile = (depsLockFilePath) => {
|
|
355
|
-
if (depsLockFilePath) {
|
|
356
|
-
const resolved = path.resolve(depsLockFilePath);
|
|
357
|
-
if (!fs.existsSync(resolved)) throw new ValidationError(`Cannot find a pnpm-lock.yaml file at '${depsLockFilePath}'.`);
|
|
358
|
-
return resolved;
|
|
359
|
-
}
|
|
360
|
-
const lockFile = findUp(PNPM_LOCK);
|
|
361
|
-
if (!lockFile) throw new ValidationError("Cannot find a pnpm-lock.yaml file. Please specify it with `depsLockFilePath`.");
|
|
362
|
-
return lockFile;
|
|
363
|
-
};
|
|
364
|
-
//#endregion
|
|
365
|
-
export { NodejsFunction };
|