@continuous-excellence/ze-great-dashboard-aws 0.7.0 → 0.8.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/README.md +4 -1
- package/dist/cli.js +112 -48
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,10 +57,13 @@ npm exec -- ze-great-dashboard-aws doctor \
|
|
|
57
57
|
|
|
58
58
|
npm exec -- ze-great-dashboard-aws package \
|
|
59
59
|
--board-config board.yaml \
|
|
60
|
+
--parameters aws-dashboard-parameters.json \
|
|
60
61
|
--output aws-dashboard-release
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
`package` writes the complete release-specific `parameters.json` and machine-readable
|
|
65
|
+
`deployment.json` alongside the ZIP and template. The deployment guide covers executing the emitted
|
|
66
|
+
S3 upload and CloudFormation commands, gateway handoff, and CI example.
|
|
64
67
|
Changing `board.yaml` or upgrading the pinned package uses this same path.
|
|
65
68
|
|
|
66
69
|
## Package boundaries
|
package/dist/cli.js
CHANGED
|
@@ -2075,34 +2075,6 @@ async function publishClientAssets(options) {
|
|
|
2075
2075
|
]);
|
|
2076
2076
|
return assetPath;
|
|
2077
2077
|
}
|
|
2078
|
-
async function deployLambda(options) {
|
|
2079
|
-
const artifactDir = resolve2(options.artifactDir);
|
|
2080
|
-
const assetsDir = resolve2(options.assetsDir);
|
|
2081
|
-
await readFile3(join2(artifactDir, "lambda.zip"));
|
|
2082
|
-
await readFile3(join2(assetsDir, "index.html"));
|
|
2083
|
-
if (options.dryRun) return;
|
|
2084
|
-
const assetPath = await publishClientAssets(options);
|
|
2085
|
-
await run("aws", [
|
|
2086
|
-
"lambda",
|
|
2087
|
-
"update-function-code",
|
|
2088
|
-
"--function-name",
|
|
2089
|
-
options.functionName,
|
|
2090
|
-
"--zip-file",
|
|
2091
|
-
`fileb://${join2(artifactDir, "lambda.zip")}`,
|
|
2092
|
-
"--no-cli-pager"
|
|
2093
|
-
]);
|
|
2094
|
-
await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
|
|
2095
|
-
await run("aws", [
|
|
2096
|
-
"lambda",
|
|
2097
|
-
"update-function-configuration",
|
|
2098
|
-
"--function-name",
|
|
2099
|
-
options.functionName,
|
|
2100
|
-
"--environment",
|
|
2101
|
-
`Variables={ASSET_PATH=${assetPath},BOARD_CONFIG_URL=./board.yaml,HOST=0.0.0.0}`,
|
|
2102
|
-
"--no-cli-pager"
|
|
2103
|
-
]);
|
|
2104
|
-
await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
|
|
2105
|
-
}
|
|
2106
2078
|
async function cloudFormationTemplate() {
|
|
2107
2079
|
return readFile3(fileURLToPath2(new URL("../template.yml", import.meta.url)), "utf8");
|
|
2108
2080
|
}
|
|
@@ -2284,7 +2256,7 @@ var requiredOption = (name, fallback) => {
|
|
|
2284
2256
|
function parameter(key, value2) {
|
|
2285
2257
|
return { ParameterKey: key, ParameterValue: value2 };
|
|
2286
2258
|
}
|
|
2287
|
-
async function existingParameters(path) {
|
|
2259
|
+
async function existingParameters(path, allowMissing = false) {
|
|
2288
2260
|
try {
|
|
2289
2261
|
const parsed = JSON.parse(await readFile5(path, "utf8"));
|
|
2290
2262
|
if (!Array.isArray(parsed)) throw new Error(`${path} must contain a JSON parameter array`);
|
|
@@ -2299,7 +2271,8 @@ async function existingParameters(path) {
|
|
|
2299
2271
|
if (new Set(keys).size !== keys.length) throw new Error(`${path} contains duplicate parameters`);
|
|
2300
2272
|
return values;
|
|
2301
2273
|
} catch (error) {
|
|
2302
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
2274
|
+
if (allowMissing && error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
2275
|
+
return [];
|
|
2303
2276
|
throw error;
|
|
2304
2277
|
}
|
|
2305
2278
|
}
|
|
@@ -2373,23 +2346,86 @@ async function templateParameters() {
|
|
|
2373
2346
|
);
|
|
2374
2347
|
return { definitions: parsed.Parameters, packageManaged };
|
|
2375
2348
|
}
|
|
2349
|
+
function validateConsumerParameters(supplied, template) {
|
|
2350
|
+
const values = Object.fromEntries(
|
|
2351
|
+
supplied.map(({ ParameterKey, ParameterValue }) => [ParameterKey, ParameterValue])
|
|
2352
|
+
);
|
|
2353
|
+
const templateKeys = Object.keys(template.definitions);
|
|
2354
|
+
const unknown = Object.keys(values).filter((key) => !templateKeys.includes(key));
|
|
2355
|
+
if (unknown.length)
|
|
2356
|
+
throw new Error(`Parameter file contains unknown parameters: ${unknown.join(", ")}`);
|
|
2357
|
+
const overridden = [...template.packageManaged].filter((key) => Object.hasOwn(values, key));
|
|
2358
|
+
if (overridden.length)
|
|
2359
|
+
throw new Error(
|
|
2360
|
+
`Parameter file must not set package-managed parameters: ${overridden.join(", ")}`
|
|
2361
|
+
);
|
|
2362
|
+
const missing = templateKeys.filter(
|
|
2363
|
+
(key) => !template.packageManaged.has(key) && !Object.hasOwn(values, key) && !Object.hasOwn(template.definitions[key] ?? {}, "Default")
|
|
2364
|
+
);
|
|
2365
|
+
if (missing.length)
|
|
2366
|
+
throw new Error(`Parameter file is missing required consumer values: ${missing.join(", ")}`);
|
|
2367
|
+
return values;
|
|
2368
|
+
}
|
|
2369
|
+
function resolvedReleaseParameters(supplied, template, managedValues) {
|
|
2370
|
+
const templateKeys = Object.keys(template.definitions);
|
|
2371
|
+
return templateKeys.map((key) => {
|
|
2372
|
+
const value2 = template.packageManaged.has(key) ? managedValues[key] : supplied[key] ?? template.definitions[key]?.Default;
|
|
2373
|
+
if (value2 === void 0) throw new Error(`No value for CloudFormation parameter ${key}`);
|
|
2374
|
+
return parameter(key, String(value2));
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
function deploymentHandoff(input) {
|
|
2378
|
+
const prefix = input.outputDir.replace(/\/+$/, "");
|
|
2379
|
+
const path = (name) => `${prefix}/${name}`;
|
|
2380
|
+
const parameterFile = path("parameters.json");
|
|
2381
|
+
return {
|
|
2382
|
+
template: "template.yml",
|
|
2383
|
+
lambdaZip: "lambda.zip",
|
|
2384
|
+
releaseFile: "release.json",
|
|
2385
|
+
artifact: { bucket: input.artifactBucket, key: input.artifactKey },
|
|
2386
|
+
release: input.release,
|
|
2387
|
+
parameters: "parameters.json",
|
|
2388
|
+
commands: {
|
|
2389
|
+
upload: [
|
|
2390
|
+
"aws",
|
|
2391
|
+
"s3",
|
|
2392
|
+
"cp",
|
|
2393
|
+
path("lambda.zip"),
|
|
2394
|
+
`s3://${input.artifactBucket}/${input.artifactKey}`,
|
|
2395
|
+
"--region",
|
|
2396
|
+
"$AWS_REGION"
|
|
2397
|
+
],
|
|
2398
|
+
deploy: [
|
|
2399
|
+
"aws",
|
|
2400
|
+
"cloudformation",
|
|
2401
|
+
"deploy",
|
|
2402
|
+
"--stack-name",
|
|
2403
|
+
"$STACK_NAME",
|
|
2404
|
+
"--template-file",
|
|
2405
|
+
path("template.yml"),
|
|
2406
|
+
"--role-arn",
|
|
2407
|
+
"$AWS_CLOUDFORMATION_EXECUTION_ROLE_ARN",
|
|
2408
|
+
"--region",
|
|
2409
|
+
"$AWS_REGION",
|
|
2410
|
+
"--capabilities",
|
|
2411
|
+
"CAPABILITY_NAMED_IAM",
|
|
2412
|
+
"--parameter-overrides",
|
|
2413
|
+
`file://${parameterFile}`,
|
|
2414
|
+
"--no-fail-on-empty-changeset",
|
|
2415
|
+
"--no-cli-pager"
|
|
2416
|
+
]
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function copyableCommand(command) {
|
|
2421
|
+
return command.map(
|
|
2422
|
+
(argument) => /^\$[A-Z][A-Z0-9_]*$/.test(argument) ? argument : `'${argument.replaceAll("'", `'\\"'\\"'`)}'`
|
|
2423
|
+
).join(" ");
|
|
2424
|
+
}
|
|
2376
2425
|
try {
|
|
2377
2426
|
const packageVersion = await installedPackageVersion();
|
|
2378
2427
|
const bundledAssets = fileURLToPath3(new URL("../client", import.meta.url));
|
|
2379
|
-
if (args[0] === "
|
|
2380
|
-
const artifactDir = requiredOption("--artifact-dir");
|
|
2381
|
-
const version = requiredOption("--version", packageVersion);
|
|
2382
|
-
await deployLambda({
|
|
2383
|
-
artifactDir,
|
|
2384
|
-
assetsDir: option("--assets-dir", bundledAssets) ?? bundledAssets,
|
|
2385
|
-
assetsBucket: requiredOption("--assets-bucket"),
|
|
2386
|
-
assetsBaseUrl: requiredOption("--assets-base-url"),
|
|
2387
|
-
functionName: requiredOption("--function-name"),
|
|
2388
|
-
version,
|
|
2389
|
-
dryRun: args.includes("--dry-run")
|
|
2390
|
-
});
|
|
2391
|
-
console.log(JSON.stringify({ deployed: true, version }));
|
|
2392
|
-
} else if (args[0] === "publish-assets") {
|
|
2428
|
+
if (args[0] === "publish-assets") {
|
|
2393
2429
|
const version = requiredOption("--version", packageVersion);
|
|
2394
2430
|
const assetPath = await publishClientAssets({
|
|
2395
2431
|
assetsDir: option("--assets-dir", bundledAssets) ?? bundledAssets,
|
|
@@ -2427,7 +2463,7 @@ try {
|
|
|
2427
2463
|
if (checks.some(({ ok }) => !ok)) process.exitCode = 1;
|
|
2428
2464
|
} else if (args[0] === "parameters") {
|
|
2429
2465
|
const output = option("--output", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
|
|
2430
|
-
const existing = await existingParameters(output);
|
|
2466
|
+
const existing = await existingParameters(output, true);
|
|
2431
2467
|
const consumerConfigPath = option("--bootstrap-config");
|
|
2432
2468
|
const consumerConfig = consumerConfigPath ? await readBootstrapConfig(consumerConfigPath) : void 0;
|
|
2433
2469
|
const existingValues = Object.fromEntries(
|
|
@@ -2713,7 +2749,7 @@ ${plan.notes.join("\n")}`);
|
|
|
2713
2749
|
}
|
|
2714
2750
|
} else if (args[0] !== "package")
|
|
2715
2751
|
throw new Error(
|
|
2716
|
-
"Usage: ze-great-dashboard-aws package|parameters|bootstrap|publish-assets|
|
|
2752
|
+
"Usage: ze-great-dashboard-aws package|parameters|bootstrap|publish-assets|doctor [options]"
|
|
2717
2753
|
);
|
|
2718
2754
|
else {
|
|
2719
2755
|
const boardConfig = option("--board-config");
|
|
@@ -2721,13 +2757,41 @@ ${plan.notes.join("\n")}`);
|
|
|
2721
2757
|
if (!boardConfig) throw new Error("--board-config is required");
|
|
2722
2758
|
if (!version)
|
|
2723
2759
|
throw new Error("Unable to determine the package version; pass --version explicitly");
|
|
2760
|
+
const outputDir = option("--output", "aws-dashboard-release") ?? "aws-dashboard-release";
|
|
2761
|
+
const parametersPath = option("--parameters", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
|
|
2762
|
+
const template = await templateParameters();
|
|
2763
|
+
const consumerParameters = validateConsumerParameters(
|
|
2764
|
+
await existingParameters(parametersPath),
|
|
2765
|
+
template
|
|
2766
|
+
);
|
|
2724
2767
|
const metadata = await packageLambda({
|
|
2725
2768
|
boardConfigPath: boardConfig,
|
|
2726
|
-
outputDir
|
|
2769
|
+
outputDir,
|
|
2727
2770
|
version,
|
|
2728
2771
|
assetDomain: option("--asset-domain")
|
|
2729
2772
|
});
|
|
2730
|
-
|
|
2773
|
+
const resolvedParameters = resolvedReleaseParameters(consumerParameters, template, {
|
|
2774
|
+
LambdaArtifactKey: metadata.artifactKey,
|
|
2775
|
+
DashboardVersion: metadata.dashboardVersion
|
|
2776
|
+
});
|
|
2777
|
+
await writeFile3(
|
|
2778
|
+
`${outputDir}/parameters.json`,
|
|
2779
|
+
`${JSON.stringify(resolvedParameters, null, 2)}
|
|
2780
|
+
`
|
|
2781
|
+
);
|
|
2782
|
+
const handoff = deploymentHandoff({
|
|
2783
|
+
outputDir,
|
|
2784
|
+
artifactBucket: resolvedParameters.find(({ ParameterKey }) => ParameterKey === "LambdaArtifactBucket")?.ParameterValue ?? "",
|
|
2785
|
+
artifactKey: metadata.artifactKey,
|
|
2786
|
+
release: metadata
|
|
2787
|
+
});
|
|
2788
|
+
await writeFile3(`${outputDir}/deployment.json`, `${JSON.stringify(handoff, null, 2)}
|
|
2789
|
+
`);
|
|
2790
|
+
console.log(`Packaged ${outputDir}/lambda.zip and ${outputDir}/template.yml`);
|
|
2791
|
+
console.log(`Complete parameters: ${outputDir}/parameters.json`);
|
|
2792
|
+
console.log(`Deployment handoff: ${outputDir}/deployment.json`);
|
|
2793
|
+
console.log(`Upload: ${copyableCommand(handoff.commands.upload)}`);
|
|
2794
|
+
console.log(`Deploy: ${copyableCommand(handoff.commands.deploy)}`);
|
|
2731
2795
|
}
|
|
2732
2796
|
} catch (error) {
|
|
2733
2797
|
console.error(error instanceof Error ? error.message : error);
|
package/dist/index.d.ts
CHANGED
|
@@ -22,15 +22,6 @@ export type LambdaPackageOptions = {
|
|
|
22
22
|
assetDomain?: string;
|
|
23
23
|
};
|
|
24
24
|
export declare function packageLambda(options: LambdaPackageOptions): Promise<PackagedRelease>;
|
|
25
|
-
export type DeployLambdaOptions = {
|
|
26
|
-
artifactDir: string;
|
|
27
|
-
assetsDir: string;
|
|
28
|
-
assetsBucket: string;
|
|
29
|
-
assetsBaseUrl: string;
|
|
30
|
-
functionName: string;
|
|
31
|
-
version: string;
|
|
32
|
-
dryRun?: boolean;
|
|
33
|
-
};
|
|
34
25
|
export type PublishClientAssetsOptions = {
|
|
35
26
|
assetsDir: string;
|
|
36
27
|
assetsBucket: string;
|
|
@@ -39,6 +30,4 @@ export type PublishClientAssetsOptions = {
|
|
|
39
30
|
};
|
|
40
31
|
/** Publishes the versioned client half of a provider-managed release. */
|
|
41
32
|
export declare function publishClientAssets(options: PublishClientAssetsOptions): Promise<string>;
|
|
42
|
-
/** Performs the AWS-specific half of a release using explicit deployment outputs. */
|
|
43
|
-
export declare function deployLambda(options: DeployLambdaOptions): Promise<void>;
|
|
44
33
|
export declare function cloudFormationTemplate(): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -2062,34 +2062,6 @@ async function publishClientAssets(options) {
|
|
|
2062
2062
|
]);
|
|
2063
2063
|
return assetPath;
|
|
2064
2064
|
}
|
|
2065
|
-
async function deployLambda(options) {
|
|
2066
|
-
const artifactDir = resolve2(options.artifactDir);
|
|
2067
|
-
const assetsDir = resolve2(options.assetsDir);
|
|
2068
|
-
await readFile3(join2(artifactDir, "lambda.zip"));
|
|
2069
|
-
await readFile3(join2(assetsDir, "index.html"));
|
|
2070
|
-
if (options.dryRun) return;
|
|
2071
|
-
const assetPath = await publishClientAssets(options);
|
|
2072
|
-
await run("aws", [
|
|
2073
|
-
"lambda",
|
|
2074
|
-
"update-function-code",
|
|
2075
|
-
"--function-name",
|
|
2076
|
-
options.functionName,
|
|
2077
|
-
"--zip-file",
|
|
2078
|
-
`fileb://${join2(artifactDir, "lambda.zip")}`,
|
|
2079
|
-
"--no-cli-pager"
|
|
2080
|
-
]);
|
|
2081
|
-
await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
|
|
2082
|
-
await run("aws", [
|
|
2083
|
-
"lambda",
|
|
2084
|
-
"update-function-configuration",
|
|
2085
|
-
"--function-name",
|
|
2086
|
-
options.functionName,
|
|
2087
|
-
"--environment",
|
|
2088
|
-
`Variables={ASSET_PATH=${assetPath},BOARD_CONFIG_URL=./board.yaml,HOST=0.0.0.0}`,
|
|
2089
|
-
"--no-cli-pager"
|
|
2090
|
-
]);
|
|
2091
|
-
await run("aws", ["lambda", "wait", "function-updated", "--function-name", options.functionName]);
|
|
2092
|
-
}
|
|
2093
2065
|
async function cloudFormationTemplate() {
|
|
2094
2066
|
return readFile3(fileURLToPath2(new URL("../template.yml", import.meta.url)), "utf8");
|
|
2095
2067
|
}
|
|
@@ -2106,7 +2078,6 @@ export {
|
|
|
2106
2078
|
checkBootstrap,
|
|
2107
2079
|
cloudFormationTemplate,
|
|
2108
2080
|
coreBootstrapOutputs,
|
|
2109
|
-
deployLambda,
|
|
2110
2081
|
deployedBootstrapStack,
|
|
2111
2082
|
formatBootstrapCheckText,
|
|
2112
2083
|
githubOidcProvider,
|