@projectdochelp/s3te 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +442 -0
- package/bin/s3te.mjs +2 -0
- package/package.json +66 -0
- package/packages/aws-adapter/src/aws-cli.mjs +102 -0
- package/packages/aws-adapter/src/deploy.mjs +433 -0
- package/packages/aws-adapter/src/features.mjs +16 -0
- package/packages/aws-adapter/src/index.mjs +7 -0
- package/packages/aws-adapter/src/manifest.mjs +88 -0
- package/packages/aws-adapter/src/package.mjs +323 -0
- package/packages/aws-adapter/src/runtime/common.mjs +917 -0
- package/packages/aws-adapter/src/runtime/content-mirror.mjs +301 -0
- package/packages/aws-adapter/src/runtime/invalidation-executor.mjs +61 -0
- package/packages/aws-adapter/src/runtime/invalidation-scheduler.mjs +59 -0
- package/packages/aws-adapter/src/runtime/render-worker.mjs +83 -0
- package/packages/aws-adapter/src/runtime/source-dispatcher.mjs +106 -0
- package/packages/aws-adapter/src/template.mjs +578 -0
- package/packages/aws-adapter/src/zip.mjs +111 -0
- package/packages/cli/bin/s3te.mjs +383 -0
- package/packages/cli/src/fs-adapters.mjs +221 -0
- package/packages/cli/src/project.mjs +535 -0
- package/packages/core/src/config.mjs +464 -0
- package/packages/core/src/content-query.mjs +176 -0
- package/packages/core/src/errors.mjs +14 -0
- package/packages/core/src/index.mjs +24 -0
- package/packages/core/src/mime.mjs +29 -0
- package/packages/core/src/minify.mjs +82 -0
- package/packages/core/src/render.mjs +537 -0
- package/packages/testkit/src/index.mjs +136 -0
- package/src/index.mjs +3 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
S3teError,
|
|
8
|
+
buildEnvironmentRuntimeConfig,
|
|
9
|
+
resolveStackName
|
|
10
|
+
} from "../../core/src/index.mjs";
|
|
11
|
+
|
|
12
|
+
import { buildAwsRuntimeManifest } from "./manifest.mjs";
|
|
13
|
+
import { resolveRequestedFeatures } from "./features.mjs";
|
|
14
|
+
import { buildCloudFormationTemplate } from "./template.mjs";
|
|
15
|
+
import { writeZipArchive } from "./zip.mjs";
|
|
16
|
+
|
|
17
|
+
const ZIP_DATE = new Date("2020-01-01T00:00:00.000Z");
|
|
18
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const RUNTIME_PACKAGE_DEPENDENCIES = [
|
|
21
|
+
"@aws-sdk/client-cloudfront",
|
|
22
|
+
"@aws-sdk/client-dynamodb",
|
|
23
|
+
"@aws-sdk/client-lambda",
|
|
24
|
+
"@aws-sdk/client-s3",
|
|
25
|
+
"@aws-sdk/client-sfn",
|
|
26
|
+
"@aws-sdk/client-ssm",
|
|
27
|
+
"@aws-sdk/lib-dynamodb",
|
|
28
|
+
"@aws-sdk/util-dynamodb"
|
|
29
|
+
];
|
|
30
|
+
const INTERNAL_RUNTIME_DIRECTORIES = [
|
|
31
|
+
{
|
|
32
|
+
sourceDir: path.resolve(PACKAGE_ROOT, "..", "core", "src"),
|
|
33
|
+
archivePrefix: "packages/core/src"
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function normalizeArchivePath(value) {
|
|
38
|
+
return String(value).replace(/\\/g, "/");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function ensureDirectory(targetDir) {
|
|
42
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function removeDirectory(targetDir) {
|
|
46
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function listFiles(rootDir, currentDir = rootDir) {
|
|
50
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
51
|
+
const files = [];
|
|
52
|
+
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
if (entry.name === "node_modules") {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
files.push(...await listFiles(rootDir, fullPath));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (entry.isFile()) {
|
|
64
|
+
files.push(path.relative(rootDir, fullPath).replace(/\\/g, "/"));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return files.sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function findPackageRoot(startPath) {
|
|
72
|
+
let currentPath = startPath;
|
|
73
|
+
|
|
74
|
+
while (true) {
|
|
75
|
+
const candidate = path.join(currentPath, "package.json");
|
|
76
|
+
try {
|
|
77
|
+
const stat = await fs.stat(candidate);
|
|
78
|
+
if (stat.isFile()) {
|
|
79
|
+
return currentPath;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// continue walking upwards
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parentPath = path.dirname(currentPath);
|
|
86
|
+
if (parentPath === currentPath) {
|
|
87
|
+
throw new Error(`Unable to locate package root from ${startPath}.`);
|
|
88
|
+
}
|
|
89
|
+
currentPath = parentPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function resolveInstalledPackageRoot(packageName) {
|
|
94
|
+
try {
|
|
95
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
96
|
+
return path.dirname(packageJsonPath);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
try {
|
|
99
|
+
const entryPath = require.resolve(packageName);
|
|
100
|
+
return await findPackageRoot(path.dirname(entryPath));
|
|
101
|
+
} catch (fallbackError) {
|
|
102
|
+
throw new S3teError(
|
|
103
|
+
"ADAPTER_ERROR",
|
|
104
|
+
`Required runtime dependency ${packageName} is not installed. Run npm install before packaging or deploying.`,
|
|
105
|
+
{ packageName, cause: fallbackError.message || error.message }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function listPackageFiles(packageRoot) {
|
|
112
|
+
const files = await listFiles(packageRoot);
|
|
113
|
+
return files.sort();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function readPackageJson(packageRoot) {
|
|
117
|
+
const raw = await fs.readFile(path.join(packageRoot, "package.json"), "utf8");
|
|
118
|
+
return JSON.parse(raw);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function collectInstalledPackageEntries(packageName, targetPrefix, seenPackages = new Set()) {
|
|
122
|
+
if (seenPackages.has(packageName)) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
seenPackages.add(packageName);
|
|
126
|
+
|
|
127
|
+
const packageRoot = await resolveInstalledPackageRoot(packageName);
|
|
128
|
+
const packageJson = await readPackageJson(packageRoot);
|
|
129
|
+
const files = await listPackageFiles(packageRoot);
|
|
130
|
+
const entries = [];
|
|
131
|
+
|
|
132
|
+
for (const relativePath of files) {
|
|
133
|
+
const absolutePath = path.join(packageRoot, relativePath);
|
|
134
|
+
const data = await fs.readFile(absolutePath);
|
|
135
|
+
entries.push({
|
|
136
|
+
name: normalizeArchivePath(path.join(targetPrefix, relativePath)),
|
|
137
|
+
data,
|
|
138
|
+
modifiedAt: ZIP_DATE
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const dependencyName of Object.keys(packageJson.dependencies ?? {}).sort()) {
|
|
143
|
+
const nestedPrefix = normalizeArchivePath(path.join("node_modules", dependencyName));
|
|
144
|
+
entries.push(...await collectInstalledPackageEntries(dependencyName, nestedPrefix, seenPackages));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return entries;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function copyDirectory(sourceDir, targetDir) {
|
|
151
|
+
const files = await listFiles(sourceDir);
|
|
152
|
+
for (const relativePath of files) {
|
|
153
|
+
const sourcePath = path.join(sourceDir, relativePath);
|
|
154
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
155
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
156
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function collectLambdaArchiveEntries() {
|
|
161
|
+
const entries = [];
|
|
162
|
+
|
|
163
|
+
const runtimeRoot = path.join(PACKAGE_ROOT, "src", "runtime");
|
|
164
|
+
const runtimeFiles = await listFiles(runtimeRoot);
|
|
165
|
+
for (const relativePath of runtimeFiles) {
|
|
166
|
+
const absolutePath = path.join(runtimeRoot, relativePath);
|
|
167
|
+
const data = await fs.readFile(absolutePath);
|
|
168
|
+
entries.push({
|
|
169
|
+
name: normalizeArchivePath(path.join("packages/aws-adapter/src/runtime", relativePath)),
|
|
170
|
+
data,
|
|
171
|
+
modifiedAt: ZIP_DATE
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const { sourceDir, archivePrefix } of INTERNAL_RUNTIME_DIRECTORIES) {
|
|
176
|
+
const sourceFiles = await listFiles(sourceDir);
|
|
177
|
+
for (const relativePath of sourceFiles) {
|
|
178
|
+
const absolutePath = path.join(sourceDir, relativePath);
|
|
179
|
+
const data = await fs.readFile(absolutePath);
|
|
180
|
+
entries.push({
|
|
181
|
+
name: normalizeArchivePath(path.join(archivePrefix, relativePath)),
|
|
182
|
+
data,
|
|
183
|
+
modifiedAt: ZIP_DATE
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const packageName of RUNTIME_PACKAGE_DEPENDENCIES) {
|
|
189
|
+
entries.push(...await collectInstalledPackageEntries(packageName, normalizeArchivePath(path.join("node_modules", packageName))));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function writeJsonFile(targetPath, value) {
|
|
196
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
197
|
+
await fs.writeFile(targetPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function prepareVariantSyncDirectory(projectDir, packageDir, runtimeConfig, variantName) {
|
|
201
|
+
const variantConfig = runtimeConfig.variants[variantName];
|
|
202
|
+
const syncRoot = path.join(packageDir, "sync", variantName);
|
|
203
|
+
await removeDirectory(syncRoot);
|
|
204
|
+
await ensureDirectory(syncRoot);
|
|
205
|
+
|
|
206
|
+
await copyDirectory(path.join(projectDir, variantConfig.partDir), path.join(syncRoot, "part"));
|
|
207
|
+
await copyDirectory(path.join(projectDir, variantConfig.sourceDir), path.join(syncRoot, variantName));
|
|
208
|
+
|
|
209
|
+
return syncRoot;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function normalizeRelative(projectDir, targetPath) {
|
|
213
|
+
return path.relative(projectDir, targetPath).replace(/\\/g, "/");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function packageAwsProject({
|
|
217
|
+
projectDir,
|
|
218
|
+
config,
|
|
219
|
+
environment,
|
|
220
|
+
outDir,
|
|
221
|
+
clean = false,
|
|
222
|
+
features = []
|
|
223
|
+
}) {
|
|
224
|
+
if (features.includes("webiny") && !config.integrations.webiny.enabled) {
|
|
225
|
+
throw new S3teError("ADAPTER_ERROR", "Feature webiny was requested but is not enabled in s3te.config.json.");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const resolvedFeatures = resolveRequestedFeatures(config, features);
|
|
229
|
+
const packageDir = outDir
|
|
230
|
+
? path.join(projectDir, outDir)
|
|
231
|
+
: path.join(projectDir, "offline", "IAAS", "package", environment);
|
|
232
|
+
if (clean) {
|
|
233
|
+
await removeDirectory(packageDir);
|
|
234
|
+
}
|
|
235
|
+
await ensureDirectory(packageDir);
|
|
236
|
+
|
|
237
|
+
const runtimeConfig = buildEnvironmentRuntimeConfig(config, environment);
|
|
238
|
+
const lambdaDir = path.join(packageDir, "lambda");
|
|
239
|
+
const templatePath = path.join(packageDir, "cloudformation.template.json");
|
|
240
|
+
const packagingManifestPath = path.join(packageDir, "manifest.json");
|
|
241
|
+
const runtimeManifestSeedPath = path.join(packageDir, "runtime-manifest.base.json");
|
|
242
|
+
const lambdaEntries = await collectLambdaArchiveEntries();
|
|
243
|
+
|
|
244
|
+
const lambdaArtifacts = {
|
|
245
|
+
sourceDispatcher: {
|
|
246
|
+
archive: path.join(lambdaDir, "source-dispatcher.zip"),
|
|
247
|
+
parameter: "SourceDispatcherArtifactKey",
|
|
248
|
+
s3Key: `lambda/source-dispatcher.zip`
|
|
249
|
+
},
|
|
250
|
+
renderWorker: {
|
|
251
|
+
archive: path.join(lambdaDir, "render-worker.zip"),
|
|
252
|
+
parameter: "RenderWorkerArtifactKey",
|
|
253
|
+
s3Key: `lambda/render-worker.zip`
|
|
254
|
+
},
|
|
255
|
+
invalidationScheduler: {
|
|
256
|
+
archive: path.join(lambdaDir, "invalidation-scheduler.zip"),
|
|
257
|
+
parameter: "InvalidationSchedulerArtifactKey",
|
|
258
|
+
s3Key: `lambda/invalidation-scheduler.zip`
|
|
259
|
+
},
|
|
260
|
+
invalidationExecutor: {
|
|
261
|
+
archive: path.join(lambdaDir, "invalidation-executor.zip"),
|
|
262
|
+
parameter: "InvalidationExecutorArtifactKey",
|
|
263
|
+
s3Key: `lambda/invalidation-executor.zip`
|
|
264
|
+
},
|
|
265
|
+
contentMirror: {
|
|
266
|
+
archive: path.join(lambdaDir, "content-mirror.zip"),
|
|
267
|
+
parameter: "ContentMirrorArtifactKey",
|
|
268
|
+
s3Key: `lambda/content-mirror.zip`
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
for (const artifact of Object.values(lambdaArtifacts)) {
|
|
273
|
+
await writeZipArchive(artifact.archive, lambdaEntries);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const syncDirectories = {};
|
|
277
|
+
for (const variantName of Object.keys(config.variants)) {
|
|
278
|
+
syncDirectories[variantName] = await prepareVariantSyncDirectory(projectDir, packageDir, runtimeConfig, variantName);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const template = buildCloudFormationTemplate({ config, environment, features: resolvedFeatures });
|
|
282
|
+
const runtimeManifestSeed = buildAwsRuntimeManifest({ config, environment });
|
|
283
|
+
|
|
284
|
+
await writeJsonFile(templatePath, template);
|
|
285
|
+
await writeJsonFile(runtimeManifestSeedPath, runtimeManifestSeed);
|
|
286
|
+
|
|
287
|
+
const manifest = {
|
|
288
|
+
version: 1,
|
|
289
|
+
environment,
|
|
290
|
+
generatedAt: new Date().toISOString(),
|
|
291
|
+
stackName: resolveStackName(config, environment),
|
|
292
|
+
runtimeParameterName: runtimeConfig.runtimeParameterName,
|
|
293
|
+
packageDir: normalizeRelative(projectDir, packageDir),
|
|
294
|
+
cloudFormationTemplate: normalizeRelative(projectDir, templatePath),
|
|
295
|
+
runtimeManifestSeed: normalizeRelative(projectDir, runtimeManifestSeedPath),
|
|
296
|
+
lambdaArtifacts: Object.fromEntries(Object.entries(lambdaArtifacts).map(([name, artifact]) => [
|
|
297
|
+
name,
|
|
298
|
+
{
|
|
299
|
+
parameter: artifact.parameter,
|
|
300
|
+
archive: normalizeRelative(projectDir, artifact.archive),
|
|
301
|
+
s3Key: artifact.s3Key
|
|
302
|
+
}
|
|
303
|
+
])),
|
|
304
|
+
syncDirectories: Object.fromEntries(Object.entries(syncDirectories).map(([variantName, syncRoot]) => [
|
|
305
|
+
variantName,
|
|
306
|
+
normalizeRelative(projectDir, syncRoot)
|
|
307
|
+
])),
|
|
308
|
+
features: {
|
|
309
|
+
available: [...resolvedFeatures],
|
|
310
|
+
requested: [...features]
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
await writeJsonFile(packagingManifestPath, manifest);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
packageDir: manifest.packageDir,
|
|
318
|
+
manifestPath: normalizeRelative(projectDir, packagingManifestPath),
|
|
319
|
+
lambdaArtifacts: Object.values(manifest.lambdaArtifacts).map((artifact) => artifact.archive),
|
|
320
|
+
cloudFormationTemplate: manifest.cloudFormationTemplate,
|
|
321
|
+
manifest
|
|
322
|
+
};
|
|
323
|
+
}
|