@alzulejos/laranja-assembly 0.2.4
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/dist/asset-hash.d.ts +16 -0
- package/dist/asset-hash.js +30 -0
- package/dist/asset-hash.js.map +1 -0
- package/dist/bundle.d.ts +31 -0
- package/dist/bundle.js +101 -0
- package/dist/bundle.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/remote-assembly.d.ts +29 -0
- package/dist/remote-assembly.js +66 -0
- package/dist/remote-assembly.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HandlerAssetHashes } from "@alzulejos/laranja-core";
|
|
2
|
+
import type { BundledHandler } from "./bundle.js";
|
|
3
|
+
/**
|
|
4
|
+
* Compute each handler's CDK asset hash, keyed by handler id.
|
|
5
|
+
*
|
|
6
|
+
* We read CDK's own `Asset.assetHash` rather than hashing the zip ourselves: CDK
|
|
7
|
+
* fingerprints the SOURCE (zips aren't byte-reproducible), and any hash we invent
|
|
8
|
+
* is ignored/rewritten by the toolkit at deploy time. Reading it through a real
|
|
9
|
+
* `Asset` guarantees the value matches exactly what the toolkit will upload —
|
|
10
|
+
* `<hash>.zip` in the bootstrap bucket — which is the key the server embeds into
|
|
11
|
+
* the template. If the two disagree, the Lambdas point at code never uploaded.
|
|
12
|
+
*
|
|
13
|
+
* Construction (not synth) computes the fingerprint, so we never call
|
|
14
|
+
* `app.synth()`; staging only ever touches a throwaway temp dir.
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeAssetHashes(handlers: BundledHandler[]): HandlerAssetHashes;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdtempSync } from "node:fs";
|
|
4
|
+
import { App, Stack } from "aws-cdk-lib";
|
|
5
|
+
import { Asset } from "aws-cdk-lib/aws-s3-assets";
|
|
6
|
+
/**
|
|
7
|
+
* Compute each handler's CDK asset hash, keyed by handler id.
|
|
8
|
+
*
|
|
9
|
+
* We read CDK's own `Asset.assetHash` rather than hashing the zip ourselves: CDK
|
|
10
|
+
* fingerprints the SOURCE (zips aren't byte-reproducible), and any hash we invent
|
|
11
|
+
* is ignored/rewritten by the toolkit at deploy time. Reading it through a real
|
|
12
|
+
* `Asset` guarantees the value matches exactly what the toolkit will upload —
|
|
13
|
+
* `<hash>.zip` in the bootstrap bucket — which is the key the server embeds into
|
|
14
|
+
* the template. If the two disagree, the Lambdas point at code never uploaded.
|
|
15
|
+
*
|
|
16
|
+
* Construction (not synth) computes the fingerprint, so we never call
|
|
17
|
+
* `app.synth()`; staging only ever touches a throwaway temp dir.
|
|
18
|
+
*/
|
|
19
|
+
export function computeAssetHashes(handlers) {
|
|
20
|
+
const outdir = mkdtempSync(path.join(os.tmpdir(), "laranja-assethash-"));
|
|
21
|
+
const app = new App({ outdir });
|
|
22
|
+
const stack = new Stack(app, "LaranjaAssetHash");
|
|
23
|
+
const hashes = {};
|
|
24
|
+
handlers.forEach((h, i) => {
|
|
25
|
+
const asset = new Asset(stack, `Asset${i}`, { path: h.assetDir });
|
|
26
|
+
hashes[h.id] = asset.assetHash;
|
|
27
|
+
});
|
|
28
|
+
return hashes;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=asset-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-hash.js","sourceRoot":"","sources":["../src/asset-hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAIlD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAA0B;IAC3D,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IACjC,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GeneratedEntry } from "@alzulejos/laranja-runtime";
|
|
2
|
+
import type { Framework } from "@alzulejos/laranja-core";
|
|
3
|
+
/** A bundled Lambda handler ready to become a CDK asset. */
|
|
4
|
+
export interface BundledHandler {
|
|
5
|
+
id: string;
|
|
6
|
+
kind: GeneratedEntry["kind"];
|
|
7
|
+
/** Absolute path to the asset directory (contains index.cjs). */
|
|
8
|
+
assetDir: string;
|
|
9
|
+
/** Lambda handler string, e.g. "index.handler". */
|
|
10
|
+
handler: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BundleOptions {
|
|
13
|
+
/** Where to write the generated entry shims. */
|
|
14
|
+
entryDir: string;
|
|
15
|
+
/** Where to write the bundles (one subdir per handler). */
|
|
16
|
+
buildDir: string;
|
|
17
|
+
/**
|
|
18
|
+
* Target framework. For "nest" the HTTP shim points at the user's COMPILED
|
|
19
|
+
* output, and we externalize Nest's optional transports + resolve its optional
|
|
20
|
+
* validation peers per-build.
|
|
21
|
+
*/
|
|
22
|
+
framework?: Framework;
|
|
23
|
+
/** User project root — used to resolve Nest's optional peers. Defaults from entryDir. */
|
|
24
|
+
projectDir?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Writes the entry shims and esbuild-bundles each into its own asset directory
|
|
28
|
+
* (so every Lambda zip contains only its own code — keeps cron/queue zips tiny).
|
|
29
|
+
* Externalizes the AWS SDK (provided by the Lambda runtime).
|
|
30
|
+
*/
|
|
31
|
+
export declare function bundleEntries(entries: GeneratedEntry[], opts: BundleOptions): Promise<BundledHandler[]>;
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { build } from "esbuild";
|
|
5
|
+
/**
|
|
6
|
+
* Optional Nest peer packages a plain HTTP proxy never loads (alternate transports
|
|
7
|
+
* + the Fastify platform). Nest lazy-requires these, so marking them external keeps
|
|
8
|
+
* esbuild from resolving/bundling transports the deployed REST app doesn't use.
|
|
9
|
+
*/
|
|
10
|
+
const NEST_TRANSPORT_EXTERNALS = [
|
|
11
|
+
"@nestjs/microservices",
|
|
12
|
+
"@nestjs/websockets",
|
|
13
|
+
"@nestjs/platform-fastify",
|
|
14
|
+
"@nestjs/platform-socket.io",
|
|
15
|
+
"@fastify/*",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Peers that `@nestjs/common` `require()`s eagerly (validation.pipe /
|
|
19
|
+
* class-serializer reference class-validator + class-transformer) but only USES
|
|
20
|
+
* lazily. esbuild resolves those static requires at bundle time and fails when
|
|
21
|
+
* they aren't installed. We can't blanket-externalize them: an app that DOES use
|
|
22
|
+
* `ValidationPipe` needs them bundled. So we resolve per-build — bundle when
|
|
23
|
+
* installed, stub to an empty module when not (Nest's own loadPackage wraps the
|
|
24
|
+
* require, so an unused stub is harmless).
|
|
25
|
+
*/
|
|
26
|
+
const NEST_OPTIONAL_PEERS = ["class-transformer", "class-validator"];
|
|
27
|
+
/**
|
|
28
|
+
* esbuild plugin: for `NEST_OPTIONAL_PEERS`, if the package resolves from the
|
|
29
|
+
* user's project, let esbuild bundle it normally; otherwise resolve it to an empty
|
|
30
|
+
* module so the bundle succeeds. Scoped to those exact packages (and their
|
|
31
|
+
* subpaths) — nothing else is touched.
|
|
32
|
+
*/
|
|
33
|
+
function nestOptionalPeersPlugin(projectDir) {
|
|
34
|
+
const req = createRequire(path.join(projectDir, "noop.js"));
|
|
35
|
+
const escaped = NEST_OPTIONAL_PEERS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
36
|
+
const filter = new RegExp(`^(${escaped.join("|")})(/|$)`);
|
|
37
|
+
return {
|
|
38
|
+
name: "nest-optional-peers",
|
|
39
|
+
setup(pluginBuild) {
|
|
40
|
+
pluginBuild.onResolve({ filter }, (args) => {
|
|
41
|
+
try {
|
|
42
|
+
req.resolve(args.path);
|
|
43
|
+
return undefined; // installed -> bundle normally
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return { path: args.path, namespace: "nest-optional-empty" };
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
pluginBuild.onLoad({ filter: /.*/, namespace: "nest-optional-empty" }, () => ({
|
|
50
|
+
contents: "module.exports = {};",
|
|
51
|
+
loader: "js",
|
|
52
|
+
}));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Writes the entry shims and esbuild-bundles each into its own asset directory
|
|
58
|
+
* (so every Lambda zip contains only its own code — keeps cron/queue zips tiny).
|
|
59
|
+
* Externalizes the AWS SDK (provided by the Lambda runtime).
|
|
60
|
+
*/
|
|
61
|
+
export async function bundleEntries(entries, opts) {
|
|
62
|
+
mkdirSync(opts.entryDir, { recursive: true });
|
|
63
|
+
for (const e of entries) {
|
|
64
|
+
writeFileSync(path.join(opts.entryDir, e.fileName), e.contents);
|
|
65
|
+
}
|
|
66
|
+
const entryPoints = {};
|
|
67
|
+
const assetNameById = new Map();
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
const assetName = e.fileName.replace(/\.ts$/, "");
|
|
70
|
+
assetNameById.set(e.id, assetName);
|
|
71
|
+
// key "<assetName>/index" -> outdir/<assetName>/index.cjs
|
|
72
|
+
entryPoints[`${assetName}/index`] = path.join(opts.entryDir, e.fileName);
|
|
73
|
+
}
|
|
74
|
+
const external = ["@aws-sdk/*", "aws-sdk"];
|
|
75
|
+
const plugins = [];
|
|
76
|
+
if (opts.framework === "nest") {
|
|
77
|
+
external.push(...NEST_TRANSPORT_EXTERNALS);
|
|
78
|
+
// entryDir is <projectDir>/.laranja/entries by convention; fall back up from it.
|
|
79
|
+
const projectDir = opts.projectDir ?? path.resolve(opts.entryDir, "..", "..");
|
|
80
|
+
plugins.push(nestOptionalPeersPlugin(projectDir));
|
|
81
|
+
}
|
|
82
|
+
await build({
|
|
83
|
+
entryPoints,
|
|
84
|
+
outdir: opts.buildDir,
|
|
85
|
+
bundle: true,
|
|
86
|
+
platform: "node",
|
|
87
|
+
target: "node20",
|
|
88
|
+
format: "cjs",
|
|
89
|
+
outExtension: { ".js": ".cjs" },
|
|
90
|
+
external,
|
|
91
|
+
plugins,
|
|
92
|
+
logLevel: "warning",
|
|
93
|
+
});
|
|
94
|
+
return entries.map((e) => ({
|
|
95
|
+
id: e.id,
|
|
96
|
+
kind: e.kind,
|
|
97
|
+
assetDir: path.join(opts.buildDir, assetNameById.get(e.id)),
|
|
98
|
+
handler: `index.${e.handlerExport}`,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { bundleEntries } from "./bundle.js";
|
|
2
|
+
export type { BundledHandler, BundleOptions } from "./bundle.js";
|
|
3
|
+
export { computeAssetHashes } from "./asset-hash.js";
|
|
4
|
+
export { assembleFromTemplate } from "./remote-assembly.js";
|
|
5
|
+
export type { RemoteAssemblyOptions } from "./remote-assembly.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BundledHandler } from "./bundle.js";
|
|
2
|
+
export interface RemoteAssemblyOptions {
|
|
3
|
+
/** Directory to write the cloud assembly (cdk.out) the toolkit deploys. */
|
|
4
|
+
outdir: string;
|
|
5
|
+
/** Stack name from the `/synth` response — must match the included template. */
|
|
6
|
+
stackName: string;
|
|
7
|
+
/** The server-synthesized CloudFormation template, parsed. */
|
|
8
|
+
template: Record<string, unknown>;
|
|
9
|
+
/** The locally bundled handlers (their dirs become the uploaded zips). */
|
|
10
|
+
handlers: BundledHandler[];
|
|
11
|
+
region?: string;
|
|
12
|
+
account?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Turn the server-synthesized CloudFormation template into a deployable cloud
|
|
16
|
+
* assembly for the CDK toolkit.
|
|
17
|
+
*
|
|
18
|
+
* The template already references each Lambda's code as `<hash>.zip` in the
|
|
19
|
+
* bootstrap bucket (the server wrote those keys from the hashes we sent). We
|
|
20
|
+
* `CfnInclude` it verbatim, then register one `Asset` per bundled handler.
|
|
21
|
+
* CfnInclude by itself uploads nothing; the Assets are what put each zip into
|
|
22
|
+
* the stack's asset manifest so the toolkit publishes them to the exact
|
|
23
|
+
* `<hash>.zip` key the template points at — the Asset recomputes the same CDK
|
|
24
|
+
* SOURCE fingerprint we sent the server, so the keys line up. We write no S3
|
|
25
|
+
* code ourselves; the toolkit does the upload.
|
|
26
|
+
*
|
|
27
|
+
* Returns the cloud-assembly directory (pass to `toolkit.fromAssemblyDirectory`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function assembleFromTemplate(opts: RemoteAssemblyOptions): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { App, Stack } from "aws-cdk-lib";
|
|
5
|
+
import { CfnInclude } from "aws-cdk-lib/cloudformation-include";
|
|
6
|
+
import { Asset } from "aws-cdk-lib/aws-s3-assets";
|
|
7
|
+
/**
|
|
8
|
+
* Turn the server-synthesized CloudFormation template into a deployable cloud
|
|
9
|
+
* assembly for the CDK toolkit.
|
|
10
|
+
*
|
|
11
|
+
* The template already references each Lambda's code as `<hash>.zip` in the
|
|
12
|
+
* bootstrap bucket (the server wrote those keys from the hashes we sent). We
|
|
13
|
+
* `CfnInclude` it verbatim, then register one `Asset` per bundled handler.
|
|
14
|
+
* CfnInclude by itself uploads nothing; the Assets are what put each zip into
|
|
15
|
+
* the stack's asset manifest so the toolkit publishes them to the exact
|
|
16
|
+
* `<hash>.zip` key the template points at — the Asset recomputes the same CDK
|
|
17
|
+
* SOURCE fingerprint we sent the server, so the keys line up. We write no S3
|
|
18
|
+
* code ourselves; the toolkit does the upload.
|
|
19
|
+
*
|
|
20
|
+
* Returns the cloud-assembly directory (pass to `toolkit.fromAssemblyDirectory`).
|
|
21
|
+
*/
|
|
22
|
+
export function assembleFromTemplate(opts) {
|
|
23
|
+
// CfnInclude reads the template from disk, so stage it to a temp file. Strip
|
|
24
|
+
// the CDK synthesizer scaffolding the server emitted (the BootstrapVersion
|
|
25
|
+
// parameter + CheckBootstrapVersion rule): our wrapping stack regenerates
|
|
26
|
+
// these identically, and CfnInclude would otherwise collide on them.
|
|
27
|
+
const template = stripSynthesizerScaffolding(opts.template);
|
|
28
|
+
const templateFile = path.join(mkdtempSync(path.join(os.tmpdir(), "laranja-remote-")), "template.json");
|
|
29
|
+
writeFileSync(templateFile, JSON.stringify(template));
|
|
30
|
+
const app = new App({ outdir: opts.outdir });
|
|
31
|
+
const stack = new Stack(app, opts.stackName, {
|
|
32
|
+
stackName: opts.stackName,
|
|
33
|
+
env: opts.account || opts.region ? { account: opts.account, region: opts.region } : undefined,
|
|
34
|
+
});
|
|
35
|
+
new CfnInclude(stack, "Template", { templateFile });
|
|
36
|
+
opts.handlers.forEach((h, i) => new Asset(stack, `Asset${i}`, { path: h.assetDir }));
|
|
37
|
+
return app.synth().directory;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Remove the CDK-synthesizer boilerplate a freshly wrapping stack will re-emit,
|
|
41
|
+
* so `CfnInclude` doesn't collide on it. These are not user infrastructure —
|
|
42
|
+
* they're the `DefaultStackSynthesizer`'s bootstrap-version guardrails, and the
|
|
43
|
+
* wrapping stack adds back byte-identical copies (same CDK version + qualifier).
|
|
44
|
+
* Returns a shallow copy; the input template is left untouched.
|
|
45
|
+
*/
|
|
46
|
+
function stripSynthesizerScaffolding(template) {
|
|
47
|
+
const out = { ...template };
|
|
48
|
+
const params = out.Parameters;
|
|
49
|
+
if (params?.BootstrapVersion) {
|
|
50
|
+
const { BootstrapVersion, ...rest } = params;
|
|
51
|
+
if (Object.keys(rest).length)
|
|
52
|
+
out.Parameters = rest;
|
|
53
|
+
else
|
|
54
|
+
delete out.Parameters;
|
|
55
|
+
}
|
|
56
|
+
const rules = out.Rules;
|
|
57
|
+
if (rules?.CheckBootstrapVersion) {
|
|
58
|
+
const { CheckBootstrapVersion, ...rest } = rules;
|
|
59
|
+
if (Object.keys(rest).length)
|
|
60
|
+
out.Rules = rest;
|
|
61
|
+
else
|
|
62
|
+
delete out.Rules;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=remote-assembly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-assembly.js","sourceRoot":"","sources":["../src/remote-assembly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAgBlD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAA2B;IAC9D,6EAA6E;IAC7E,2EAA2E;IAC3E,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,QAAQ,GAAG,2BAA2B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IACxG,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEtD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE;QAC3C,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;KAC9F,CAAC,CAAC;IACH,IAAI,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAErF,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,SAAS,2BAA2B,CAAC,QAAiC;IACpE,MAAM,GAAG,GAA4B,EAAE,GAAG,QAAQ,EAAE,CAAC;IAErD,MAAM,MAAM,GAAG,GAAG,CAAC,UAAiD,CAAC;IACrE,IAAI,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAC7B,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;QAC7C,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM;YAAE,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;;YAC/C,OAAO,GAAG,CAAC,UAAU,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,KAA4C,CAAC;IAC/D,IAAI,KAAK,EAAE,qBAAqB,EAAE,CAAC;QACjC,MAAM,EAAE,qBAAqB,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QACjD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM;YAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;;YAC1C,OAAO,GAAG,CAAC,KAAK,CAAC;IACxB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alzulejos/laranja-assembly",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@alzulejos/laranja-core": "0.2.4",
|
|
22
|
+
"@alzulejos/laranja-runtime": "0.2.4",
|
|
23
|
+
"aws-cdk-lib": "^2.160.0",
|
|
24
|
+
"constructs": "^10.3.0",
|
|
25
|
+
"esbuild": "^0.28.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@alzulejos/laranja-scanner": "0.2.4"
|
|
29
|
+
}
|
|
30
|
+
}
|