@fjall/deploy-core 0.89.2
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/dist/src/aws/AwsProvider.d.ts +39 -0
- package/dist/src/aws/AwsProvider.js +1 -0
- package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
- package/dist/src/aws/SimpleAwsProvider.js +73 -0
- package/dist/src/aws/index.d.ts +4 -0
- package/dist/src/aws/index.js +3 -0
- package/dist/src/aws/organisations/accounts.d.ts +21 -0
- package/dist/src/aws/organisations/accounts.js +99 -0
- package/dist/src/aws/organisations/backup.d.ts +12 -0
- package/dist/src/aws/organisations/backup.js +28 -0
- package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
- package/dist/src/aws/organisations/costAllocation.js +26 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
- package/dist/src/aws/organisations/identityCentre.js +19 -0
- package/dist/src/aws/organisations/index.d.ts +16 -0
- package/dist/src/aws/organisations/index.js +12 -0
- package/dist/src/aws/organisations/ipam.d.ts +7 -0
- package/dist/src/aws/organisations/ipam.js +18 -0
- package/dist/src/aws/organisations/organisation.d.ts +12 -0
- package/dist/src/aws/organisations/organisation.js +94 -0
- package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
- package/dist/src/aws/organisations/organisationalUnits.js +125 -0
- package/dist/src/aws/organisations/policies.d.ts +7 -0
- package/dist/src/aws/organisations/policies.js +36 -0
- package/dist/src/aws/organisations/ram.d.ts +7 -0
- package/dist/src/aws/organisations/ram.js +15 -0
- package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
- package/dist/src/aws/organisations/serviceAccess.js +38 -0
- package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
- package/dist/src/aws/organisations/trustedAccess.js +15 -0
- package/dist/src/aws/organisations/types.d.ts +29 -0
- package/dist/src/aws/organisations/types.js +16 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
- package/dist/src/aws/utils/cloudformationEvents.js +596 -0
- package/dist/src/aws/utils/errors.d.ts +26 -0
- package/dist/src/aws/utils/errors.js +59 -0
- package/dist/src/aws/utils/regions.d.ts +1 -0
- package/dist/src/aws/utils/regions.js +1 -0
- package/dist/src/aws/utils/stackStatus.d.ts +23 -0
- package/dist/src/aws/utils/stackStatus.js +90 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +45 -0
- package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
- package/dist/src/orchestration/applicationDeploy.js +327 -0
- package/dist/src/orchestration/contextHelpers.d.ts +9 -0
- package/dist/src/orchestration/contextHelpers.js +14 -0
- package/dist/src/orchestration/deploy.d.ts +10 -0
- package/dist/src/orchestration/deploy.js +42 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
- package/dist/src/orchestration/detectionPipeline.js +65 -0
- package/dist/src/orchestration/dockerInterface.d.ts +56 -0
- package/dist/src/orchestration/dockerInterface.js +1 -0
- package/dist/src/orchestration/domainInterface.d.ts +37 -0
- package/dist/src/orchestration/domainInterface.js +1 -0
- package/dist/src/orchestration/index.d.ts +8 -0
- package/dist/src/orchestration/index.js +3 -0
- package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
- package/dist/src/orchestration/organisationDeploy.js +382 -0
- package/dist/src/orchestration/organisationSetup.d.ts +42 -0
- package/dist/src/orchestration/organisationSetup.js +227 -0
- package/dist/src/orchestration/resolveOperation.d.ts +10 -0
- package/dist/src/orchestration/resolveOperation.js +53 -0
- package/dist/src/orchestration/serviceFactory.d.ts +15 -0
- package/dist/src/orchestration/serviceFactory.js +16 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
- package/dist/src/services/application/ApplicationStackService.js +436 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
- package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
- package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
- package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
- package/dist/src/services/infrastructure/CdkService.js +254 -0
- package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
- package/dist/src/services/infrastructure/index.d.ts +8 -0
- package/dist/src/services/infrastructure/index.js +7 -0
- package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
- package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
- package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
- package/dist/src/services/supporting/TemplateHashService.js +152 -0
- package/dist/src/services/supporting/helpers.d.ts +46 -0
- package/dist/src/services/supporting/helpers.js +81 -0
- package/dist/src/services/supporting/index.d.ts +3 -0
- package/dist/src/services/supporting/index.js +3 -0
- package/dist/src/types/FjallState.d.ts +50 -0
- package/dist/src/types/FjallState.js +118 -0
- package/dist/src/types/ProgressEvent.d.ts +35 -0
- package/dist/src/types/ProgressEvent.js +48 -0
- package/dist/src/types/apiClient.d.ts +34 -0
- package/dist/src/types/apiClient.js +1 -0
- package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
- package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
- package/dist/src/types/application/index.d.ts +1 -0
- package/dist/src/types/application/index.js +1 -0
- package/dist/src/types/callbacks.d.ts +36 -0
- package/dist/src/types/callbacks.js +1 -0
- package/dist/src/types/constants.d.ts +6 -0
- package/dist/src/types/constants.js +6 -0
- package/dist/src/types/credentials.d.ts +30 -0
- package/dist/src/types/credentials.js +1 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
- package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
- package/dist/src/types/deployment/DeploymentTypes.js +1 -0
- package/dist/src/types/deployment/cloudformation.d.ts +14 -0
- package/dist/src/types/deployment/cloudformation.js +1 -0
- package/dist/src/types/deployment/index.d.ts +5 -0
- package/dist/src/types/deployment/index.js +1 -0
- package/dist/src/types/deployment/parallel.d.ts +46 -0
- package/dist/src/types/deployment/parallel.js +10 -0
- package/dist/src/types/errors/CdkError.d.ts +14 -0
- package/dist/src/types/errors/CdkError.js +20 -0
- package/dist/src/types/errors/ServiceError.d.ts +86 -0
- package/dist/src/types/errors/ServiceError.js +119 -0
- package/dist/src/types/events.d.ts +40 -0
- package/dist/src/types/events.js +5 -0
- package/dist/src/types/index.d.ts +20 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/operations.d.ts +193 -0
- package/dist/src/types/operations.js +285 -0
- package/dist/src/types/orgConfig.d.ts +28 -0
- package/dist/src/types/orgConfig.js +11 -0
- package/dist/src/types/params.d.ts +74 -0
- package/dist/src/types/params.js +1 -0
- package/dist/src/types/patternDetection.d.ts +43 -0
- package/dist/src/types/patternDetection.js +92 -0
- package/dist/src/types/validation.d.ts +12 -0
- package/dist/src/types/validation.js +1 -0
- package/dist/src/util/fsHelpers.d.ts +4 -0
- package/dist/src/util/fsHelpers.js +16 -0
- package/dist/src/util/index.d.ts +3 -0
- package/dist/src/util/index.js +3 -0
- package/dist/src/util/securityHelpers.d.ts +31 -0
- package/dist/src/util/securityHelpers.js +124 -0
- package/dist/src/util/singleton.d.ts +2 -0
- package/dist/src/util/singleton.js +9 -0
- package/dist/src/util/sleep.d.ts +4 -0
- package/dist/src/util/sleep.js +4 -0
- package/package.json +42 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProgressEvent } from "../../types/events.js";
|
|
2
|
+
/**
|
|
3
|
+
* Emits a progress event through the callbacks if available.
|
|
4
|
+
*/
|
|
5
|
+
export declare function emitProgress(callbacks: {
|
|
6
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
7
|
+
} | undefined, message: string, type?: ProgressEvent["type"]): void;
|
|
8
|
+
/** Progress message constants — avoids magic string repetition */
|
|
9
|
+
export declare const PROGRESS_MESSAGES: Readonly<{
|
|
10
|
+
readonly BUILD: "Building Next.js application";
|
|
11
|
+
readonly SYNTH: "Synthesising infrastructure";
|
|
12
|
+
readonly HASH: "Comparing template hashes";
|
|
13
|
+
readonly DOCKER: "Validating Docker configuration";
|
|
14
|
+
readonly SECRETS: "Validating SSM secrets";
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Parse Next.js/OpenNext build output to extract meaningful phase transitions.
|
|
18
|
+
* Returns a clean status message for display instead of raw build output.
|
|
19
|
+
*
|
|
20
|
+
* Only updates when a meaningful phase change is detected to avoid UI flicker.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseBuildPhase(message: string, currentPhase: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Build the StepContext build configuration from a buildConfig object.
|
|
25
|
+
* Extracts the database-related fields with defaults.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildStepContextBuildConfig(buildConfig: {
|
|
28
|
+
requiresDatabase?: boolean;
|
|
29
|
+
databaseEnvVar?: string;
|
|
30
|
+
} | undefined): {
|
|
31
|
+
requiresDatabase: boolean;
|
|
32
|
+
databaseEnvVar: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* CloudFormation output shape (matches AWS SDK Output type).
|
|
36
|
+
*/
|
|
37
|
+
export interface CloudFormationOutput {
|
|
38
|
+
OutputKey?: string;
|
|
39
|
+
OutputValue?: string;
|
|
40
|
+
ExportName?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Convert CloudFormation outputs array to a key-value record.
|
|
44
|
+
* Filters out outputs without keys and handles undefined values.
|
|
45
|
+
*/
|
|
46
|
+
export declare function convertCloudFormationOutputsToRecord(outputs: CloudFormationOutput[]): Record<string, string | number | boolean>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emits a progress event through the callbacks if available.
|
|
3
|
+
*/
|
|
4
|
+
export function emitProgress(callbacks, message, type = "info") {
|
|
5
|
+
callbacks?.onProgress?.({ type, message });
|
|
6
|
+
}
|
|
7
|
+
/** Progress message constants — avoids magic string repetition */
|
|
8
|
+
export const PROGRESS_MESSAGES = Object.freeze({
|
|
9
|
+
BUILD: "Building Next.js application",
|
|
10
|
+
SYNTH: "Synthesising infrastructure",
|
|
11
|
+
HASH: "Comparing template hashes",
|
|
12
|
+
DOCKER: "Validating Docker configuration",
|
|
13
|
+
SECRETS: "Validating SSM secrets"
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Parse Next.js/OpenNext build output to extract meaningful phase transitions.
|
|
17
|
+
* Returns a clean status message for display instead of raw build output.
|
|
18
|
+
*
|
|
19
|
+
* Only updates when a meaningful phase change is detected to avoid UI flicker.
|
|
20
|
+
*/
|
|
21
|
+
export function parseBuildPhase(message, currentPhase) {
|
|
22
|
+
const lowerMessage = message.toLowerCase();
|
|
23
|
+
if (lowerMessage.includes("creating next.js build") ||
|
|
24
|
+
lowerMessage.includes("creating an optimised production build")) {
|
|
25
|
+
return "Creating production build...";
|
|
26
|
+
}
|
|
27
|
+
if (lowerMessage.includes("compiling")) {
|
|
28
|
+
return "Compiling...";
|
|
29
|
+
}
|
|
30
|
+
if (lowerMessage.includes("linting")) {
|
|
31
|
+
return "Linting...";
|
|
32
|
+
}
|
|
33
|
+
if (lowerMessage.includes("checking validity of types")) {
|
|
34
|
+
return "Type checking...";
|
|
35
|
+
}
|
|
36
|
+
if (lowerMessage.includes("collecting page data")) {
|
|
37
|
+
return "Collecting page data...";
|
|
38
|
+
}
|
|
39
|
+
if (lowerMessage.includes("generating static pages")) {
|
|
40
|
+
return "Generating static pages...";
|
|
41
|
+
}
|
|
42
|
+
if (lowerMessage.includes("finalizing page optimization")) {
|
|
43
|
+
return "Optimising pages...";
|
|
44
|
+
}
|
|
45
|
+
if (lowerMessage.includes("open-next build complete") ||
|
|
46
|
+
lowerMessage.includes("build completed")) {
|
|
47
|
+
return "Build complete";
|
|
48
|
+
}
|
|
49
|
+
if (lowerMessage.includes("bundling")) {
|
|
50
|
+
return "Bundling functions...";
|
|
51
|
+
}
|
|
52
|
+
if (lowerMessage.includes("copying assets")) {
|
|
53
|
+
return "Copying assets...";
|
|
54
|
+
}
|
|
55
|
+
return currentPhase;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the StepContext build configuration from a buildConfig object.
|
|
59
|
+
* Extracts the database-related fields with defaults.
|
|
60
|
+
*/
|
|
61
|
+
export function buildStepContextBuildConfig(buildConfig) {
|
|
62
|
+
if (!buildConfig)
|
|
63
|
+
return undefined;
|
|
64
|
+
return {
|
|
65
|
+
requiresDatabase: buildConfig.requiresDatabase ?? false,
|
|
66
|
+
databaseEnvVar: buildConfig.databaseEnvVar ?? "DATABASE_URL"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Convert CloudFormation outputs array to a key-value record.
|
|
71
|
+
* Filters out outputs without keys and handles undefined values.
|
|
72
|
+
*/
|
|
73
|
+
export function convertCloudFormationOutputsToRecord(outputs) {
|
|
74
|
+
const result = {};
|
|
75
|
+
for (const output of outputs) {
|
|
76
|
+
if (output.OutputKey && output.OutputValue !== undefined) {
|
|
77
|
+
result[output.OutputKey] = output.OutputValue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { TemplateHashService, TemplateHashError, type TemplateComparisonResult } from "./TemplateHashService.js";
|
|
2
|
+
export { CdkContextBuilder } from "./CdkContextBuilder.js";
|
|
3
|
+
export { emitProgress, PROGRESS_MESSAGES, parseBuildPhase, buildStepContextBuildConfig, convertCloudFormationOutputsToRecord, type CloudFormationOutput } from "./helpers.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { TemplateHashService, TemplateHashError } from "./TemplateHashService.js";
|
|
2
|
+
export { CdkContextBuilder } from "./CdkContextBuilder.js";
|
|
3
|
+
export { emitProgress, PROGRESS_MESSAGES, parseBuildPhase, buildStepContextBuildConfig, convertCloudFormationOutputsToRecord } from "./helpers.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const TemplateHashEntrySchema: z.ZodObject<{
|
|
3
|
+
hash: z.ZodString;
|
|
4
|
+
deployedAt: z.ZodString;
|
|
5
|
+
stackStatus: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strict>;
|
|
7
|
+
/**
|
|
8
|
+
* State file schema for hash-based change detection.
|
|
9
|
+
* Location: fjall/{appName}/.fjall-state.json
|
|
10
|
+
*/
|
|
11
|
+
export declare const FjallStateFileSchema: z.ZodObject<{
|
|
12
|
+
version: z.ZodLiteral<1>;
|
|
13
|
+
lastDeployedAt: z.ZodOptional<z.ZodString>;
|
|
14
|
+
templateHashes: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
+
hash: z.ZodString;
|
|
16
|
+
deployedAt: z.ZodString;
|
|
17
|
+
stackStatus: z.ZodOptional<z.ZodString>;
|
|
18
|
+
}, z.core.$strict>>;
|
|
19
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
|
+
}, z.core.$strict>;
|
|
21
|
+
export type TemplateHashEntry = z.infer<typeof TemplateHashEntrySchema>;
|
|
22
|
+
export type FjallStateFile = z.infer<typeof FjallStateFileSchema>;
|
|
23
|
+
/**
|
|
24
|
+
* Get the state file path for an application
|
|
25
|
+
*/
|
|
26
|
+
export declare function getStateFilePath(appPath: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Read state file from application directory.
|
|
29
|
+
* Returns null if file doesn't exist or is corrupt (graceful degradation).
|
|
30
|
+
*/
|
|
31
|
+
export declare function readStateFile(appPath: string): Promise<FjallStateFile | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Write state file atomically using temp file + rename pattern.
|
|
34
|
+
* Prevents partial writes from corrupting the state file.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeStateFile(appPath: string, state: FjallStateFile): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Create an empty state file structure
|
|
39
|
+
*/
|
|
40
|
+
export declare function createEmptyState(): FjallStateFile;
|
|
41
|
+
/**
|
|
42
|
+
* Delete state file for an application directory.
|
|
43
|
+
* No-op if the file does not exist.
|
|
44
|
+
*/
|
|
45
|
+
export declare function deleteStateFile(appPath: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Update a single template hash in the state
|
|
48
|
+
*/
|
|
49
|
+
export declare function updateTemplateHash(state: FjallStateFile, stackName: string, hash: string, stackStatus?: string): FjallStateFile;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile, writeFile, unlink, rename, mkdir } from "fs/promises";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileExists } from "../util/fsHelpers.js";
|
|
5
|
+
import { logger, getErrorMessage } from "@fjall/util";
|
|
6
|
+
const TemplateHashEntrySchema = z
|
|
7
|
+
.object({
|
|
8
|
+
hash: z.string(),
|
|
9
|
+
deployedAt: z.string(),
|
|
10
|
+
stackStatus: z.string().optional()
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
/**
|
|
14
|
+
* State file schema for hash-based change detection.
|
|
15
|
+
* Location: fjall/{appName}/.fjall-state.json
|
|
16
|
+
*/
|
|
17
|
+
export const FjallStateFileSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
version: z.literal(1),
|
|
20
|
+
lastDeployedAt: z.string().optional(),
|
|
21
|
+
templateHashes: z.record(z.string(), TemplateHashEntrySchema),
|
|
22
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
const STATE_FILE_NAME = ".fjall-state.json";
|
|
26
|
+
/**
|
|
27
|
+
* Get the state file path for an application
|
|
28
|
+
*/
|
|
29
|
+
export function getStateFilePath(appPath) {
|
|
30
|
+
return join(appPath, STATE_FILE_NAME);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read state file from application directory.
|
|
34
|
+
* Returns null if file doesn't exist or is corrupt (graceful degradation).
|
|
35
|
+
*/
|
|
36
|
+
export async function readStateFile(appPath) {
|
|
37
|
+
const statePath = getStateFilePath(appPath);
|
|
38
|
+
if (!(await fileExists(statePath))) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const content = await readFile(statePath, "utf-8");
|
|
43
|
+
const parsed = JSON.parse(content);
|
|
44
|
+
const result = FjallStateFileSchema.safeParse(parsed);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return result.data;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger.debug("FjallState", "Failed to read state file", {
|
|
52
|
+
path: statePath,
|
|
53
|
+
error: getErrorMessage(err)
|
|
54
|
+
});
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write state file atomically using temp file + rename pattern.
|
|
60
|
+
* Prevents partial writes from corrupting the state file.
|
|
61
|
+
*/
|
|
62
|
+
export async function writeStateFile(appPath, state) {
|
|
63
|
+
const statePath = getStateFilePath(appPath);
|
|
64
|
+
const tempPath = `${statePath}.${Date.now()}.tmp`;
|
|
65
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
66
|
+
try {
|
|
67
|
+
await writeFile(tempPath, JSON.stringify(state, null, 2), "utf-8");
|
|
68
|
+
await rename(tempPath, statePath);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
try {
|
|
72
|
+
await unlink(tempPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Temp file cleanup is non-fatal
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create an empty state file structure
|
|
82
|
+
*/
|
|
83
|
+
export function createEmptyState() {
|
|
84
|
+
return {
|
|
85
|
+
version: 1,
|
|
86
|
+
templateHashes: {}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Delete state file for an application directory.
|
|
91
|
+
* No-op if the file does not exist.
|
|
92
|
+
*/
|
|
93
|
+
export async function deleteStateFile(appPath) {
|
|
94
|
+
const statePath = getStateFilePath(appPath);
|
|
95
|
+
try {
|
|
96
|
+
await unlink(statePath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* file may not exist */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Update a single template hash in the state
|
|
104
|
+
*/
|
|
105
|
+
export function updateTemplateHash(state, stackName, hash, stackStatus) {
|
|
106
|
+
return {
|
|
107
|
+
...state,
|
|
108
|
+
lastDeployedAt: new Date().toISOString(),
|
|
109
|
+
templateHashes: {
|
|
110
|
+
...state.templateHashes,
|
|
111
|
+
[stackName]: {
|
|
112
|
+
hash,
|
|
113
|
+
deployedAt: new Date().toISOString(),
|
|
114
|
+
...(stackStatus !== undefined && { stackStatus })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress callbacks used by infrastructure services.
|
|
3
|
+
*
|
|
4
|
+
* This is the simpler callback interface used by inner services
|
|
5
|
+
* (CdkService, CloudFormationService). The orchestration layer
|
|
6
|
+
* bridges between DeployCallbacks and ProgressCallbacks.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProgressEvent } from "./events.js";
|
|
9
|
+
export type { ProgressEvent, ProgressEventType } from "./events.js";
|
|
10
|
+
export interface ProgressCallbacks {
|
|
11
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
12
|
+
onWarning?: (message: string, callback: (proceed: boolean) => void) => void;
|
|
13
|
+
onError?: (error: Error) => void;
|
|
14
|
+
directConsole?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Helper to create progress events with optional typing
|
|
18
|
+
*/
|
|
19
|
+
export declare class ProgressReporter {
|
|
20
|
+
private callbacks?;
|
|
21
|
+
private stepIndex;
|
|
22
|
+
private totalSteps?;
|
|
23
|
+
constructor(callbacks?: ProgressCallbacks | undefined);
|
|
24
|
+
setTotalSteps(total: number): void;
|
|
25
|
+
step(message: string, options?: {
|
|
26
|
+
stepIndex?: number;
|
|
27
|
+
totalSteps?: number;
|
|
28
|
+
metadata?: ProgressEvent["metadata"];
|
|
29
|
+
}): void;
|
|
30
|
+
info(message: string, metadata?: ProgressEvent["metadata"]): void;
|
|
31
|
+
spinner(message: string, metadata?: ProgressEvent["metadata"]): void;
|
|
32
|
+
warning(message: string, metadata?: ProgressEvent["metadata"]): void;
|
|
33
|
+
debug(message: string, metadata?: ProgressEvent["metadata"]): void;
|
|
34
|
+
error(message: string, metadata?: ProgressEvent["metadata"]): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress callbacks used by infrastructure services.
|
|
3
|
+
*
|
|
4
|
+
* This is the simpler callback interface used by inner services
|
|
5
|
+
* (CdkService, CloudFormationService). The orchestration layer
|
|
6
|
+
* bridges between DeployCallbacks and ProgressCallbacks.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Helper to create progress events with optional typing
|
|
10
|
+
*/
|
|
11
|
+
export class ProgressReporter {
|
|
12
|
+
callbacks;
|
|
13
|
+
stepIndex = 0;
|
|
14
|
+
totalSteps;
|
|
15
|
+
constructor(callbacks) {
|
|
16
|
+
this.callbacks = callbacks;
|
|
17
|
+
}
|
|
18
|
+
setTotalSteps(total) {
|
|
19
|
+
this.totalSteps = total;
|
|
20
|
+
}
|
|
21
|
+
step(message, options) {
|
|
22
|
+
const { metadata, stepIndex, totalSteps } = options ?? {};
|
|
23
|
+
this.callbacks?.onProgress?.({
|
|
24
|
+
type: "step",
|
|
25
|
+
message,
|
|
26
|
+
metadata: {
|
|
27
|
+
...metadata,
|
|
28
|
+
stepIndex: stepIndex ?? this.stepIndex++,
|
|
29
|
+
totalSteps: totalSteps ?? this.totalSteps
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
info(message, metadata) {
|
|
34
|
+
this.callbacks?.onProgress?.({ type: "info", message, metadata });
|
|
35
|
+
}
|
|
36
|
+
spinner(message, metadata) {
|
|
37
|
+
this.callbacks?.onProgress?.({ type: "spinner", message, metadata });
|
|
38
|
+
}
|
|
39
|
+
warning(message, metadata) {
|
|
40
|
+
this.callbacks?.onProgress?.({ type: "warning", message, metadata });
|
|
41
|
+
}
|
|
42
|
+
debug(message, metadata) {
|
|
43
|
+
this.callbacks?.onProgress?.({ type: "debug", message, metadata });
|
|
44
|
+
}
|
|
45
|
+
error(message, metadata) {
|
|
46
|
+
this.callbacks?.onProgress?.({ type: "error", message, metadata });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Result } from "@fjall/generator";
|
|
2
|
+
/**
|
|
3
|
+
* Interface for non-critical API operations.
|
|
4
|
+
*
|
|
5
|
+
* CLI provides a real implementation (FjallApiClient).
|
|
6
|
+
* Worker omits this — non-critical operations are handled
|
|
7
|
+
* by the webapp directly (it has DB access).
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiClientInterface {
|
|
10
|
+
getIdentity(): Promise<Result<{
|
|
11
|
+
organisationId: string;
|
|
12
|
+
organisationName: string;
|
|
13
|
+
}>>;
|
|
14
|
+
getEntitlements(): Promise<Result<EntitlementsData>>;
|
|
15
|
+
verifyAwsAccount(data: {
|
|
16
|
+
awsAccountId: string;
|
|
17
|
+
accountName: string;
|
|
18
|
+
}): Promise<Result<unknown>>;
|
|
19
|
+
putOrganisationConfig(data: Record<string, unknown>): Promise<Result<unknown>>;
|
|
20
|
+
createApplication(data: Record<string, unknown>): Promise<Result<unknown>>;
|
|
21
|
+
createActivity(data: Record<string, unknown>): Promise<Result<unknown>>;
|
|
22
|
+
}
|
|
23
|
+
export interface EntitlementsData {
|
|
24
|
+
plan: string;
|
|
25
|
+
limits: {
|
|
26
|
+
awsAccounts: number;
|
|
27
|
+
applications: number;
|
|
28
|
+
};
|
|
29
|
+
usage: {
|
|
30
|
+
awsAccounts: number;
|
|
31
|
+
applications: number;
|
|
32
|
+
};
|
|
33
|
+
features: Record<string, boolean>;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ApplicationStack } from "../operations.js";
|
|
2
|
+
import type { ResourceEvent } from "../events.js";
|
|
3
|
+
import { BaseServiceError } from "../errors/ServiceError.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wraps an error into an ApplicationError with consistent formatting.
|
|
6
|
+
*/
|
|
7
|
+
export declare function wrapApplicationError(message: string, errorType: ApplicationErrorType, appName: string | undefined, operation: string, error: unknown): ApplicationError;
|
|
8
|
+
export interface StackDeploymentOptions {
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
onProgress?: (message: string) => void;
|
|
11
|
+
onResourceProgress?: (event: ResourceEvent) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface StackDeploymentData {
|
|
14
|
+
stackName: string;
|
|
15
|
+
stackType: ApplicationStack;
|
|
16
|
+
skipped?: boolean;
|
|
17
|
+
outputs?: Record<string, string | number | boolean>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Application deployment result data
|
|
21
|
+
*/
|
|
22
|
+
export interface ApplicationDeploymentData {
|
|
23
|
+
stacks: StackDeploymentData[];
|
|
24
|
+
dockerDeployed?: boolean;
|
|
25
|
+
ecrInitialised?: boolean;
|
|
26
|
+
message?: string;
|
|
27
|
+
websiteUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Application destruction result data
|
|
31
|
+
*/
|
|
32
|
+
export interface ApplicationDestructionData {
|
|
33
|
+
stacksDestroyed: string[];
|
|
34
|
+
skippedStacks: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Application error types for categorised error handling
|
|
38
|
+
*/
|
|
39
|
+
export type ApplicationErrorType = "auth_failed" | "detection_failed" | "validation_failed" | "synth_failed" | "deployment_failed" | "destroy_failed" | "docker_failed" | "ecr_failed" | "stack_error" | "config_error" | "unknown";
|
|
40
|
+
/**
|
|
41
|
+
* Application-specific error with typed error categories
|
|
42
|
+
*/
|
|
43
|
+
export declare class ApplicationError extends BaseServiceError {
|
|
44
|
+
readonly errorType: ApplicationErrorType;
|
|
45
|
+
readonly appName?: string;
|
|
46
|
+
readonly operation?: string;
|
|
47
|
+
readonly stackType?: ApplicationStack;
|
|
48
|
+
constructor(message: string, options: {
|
|
49
|
+
errorType: ApplicationErrorType;
|
|
50
|
+
appName?: string;
|
|
51
|
+
operation?: string;
|
|
52
|
+
stackType?: ApplicationStack;
|
|
53
|
+
details?: unknown;
|
|
54
|
+
recoverable?: boolean;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseServiceError } from "../errors/ServiceError.js";
|
|
2
|
+
import { getErrorMessage } from "@fjall/util";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an error into an ApplicationError with consistent formatting.
|
|
5
|
+
*/
|
|
6
|
+
export function wrapApplicationError(message, errorType, appName, operation, error) {
|
|
7
|
+
const errorMsg = getErrorMessage(error);
|
|
8
|
+
return new ApplicationError(`${message}: ${errorMsg}`, {
|
|
9
|
+
errorType,
|
|
10
|
+
appName,
|
|
11
|
+
operation,
|
|
12
|
+
details: error
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Application-specific error with typed error categories
|
|
17
|
+
*/
|
|
18
|
+
export class ApplicationError extends BaseServiceError {
|
|
19
|
+
errorType;
|
|
20
|
+
appName;
|
|
21
|
+
operation;
|
|
22
|
+
stackType;
|
|
23
|
+
constructor(message, options) {
|
|
24
|
+
super(`APPLICATION_${options.errorType.toUpperCase()}`, message, options.details, options.recoverable ?? false);
|
|
25
|
+
this.errorType = options.errorType;
|
|
26
|
+
this.appName = options.appName;
|
|
27
|
+
this.operation = options.operation;
|
|
28
|
+
this.stackType = options.stackType;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ApplicationError, wrapApplicationError, type ApplicationErrorType, type StackDeploymentData, type StackDeploymentOptions, type ApplicationDeploymentData, type ApplicationDestructionData } from "./ApplicationServiceTypes.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ApplicationError, wrapApplicationError } from "./ApplicationServiceTypes.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ProgressEvent, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase } from "./events.js";
|
|
2
|
+
export type StepCompleteStatus = "completed" | "skipped" | "error";
|
|
3
|
+
/**
|
|
4
|
+
* Unified callback interface for deployment progress.
|
|
5
|
+
*
|
|
6
|
+
* CLI adapter: formats for terminal output via OutputWriter.
|
|
7
|
+
* Worker adapter: writes events to DB and streams via SSE.
|
|
8
|
+
*
|
|
9
|
+
* All callbacks are optional. Omitting a callback means that
|
|
10
|
+
* event type is silently ignored.
|
|
11
|
+
*/
|
|
12
|
+
export interface DeployCallbacks {
|
|
13
|
+
onStepStart?: (stepId: string, stepName: string, stepIndex?: number, stepTotal?: number) => void;
|
|
14
|
+
onStepComplete?: (stepId: string, stepName: string, status: StepCompleteStatus, stepIndex?: number, stepTotal?: number) => void;
|
|
15
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
16
|
+
onLog?: (message: string, level?: "info" | "debug" | "warn") => void;
|
|
17
|
+
onOutput?: (chunk: string) => void;
|
|
18
|
+
onResourceProgress?: (event: ResourceEvent) => void;
|
|
19
|
+
onDockerProgress?: (message: string, percentage?: number, layerId?: string, status?: string) => void;
|
|
20
|
+
onECSUpdate?: (status: string, service?: string) => void;
|
|
21
|
+
onECSProgress?: (message: string, percentage?: number) => void;
|
|
22
|
+
onCDKBootstrap?: (status: string) => void;
|
|
23
|
+
onCDKDiff?: (hasDifferences: boolean) => void;
|
|
24
|
+
onCdkOutput?: (output: string, type: "synth" | "diff") => void;
|
|
25
|
+
onCascadeStart?: () => void;
|
|
26
|
+
onCascadePhaseStart?: (phase: CascadePhase) => void;
|
|
27
|
+
onCascadeAccountStart?: (operationKey: string, accountId: string, region: string) => void;
|
|
28
|
+
onCascadeAccountPhaseChange?: (operationKey: string, phase: "synth" | "deploy" | "destroy", region?: string) => void;
|
|
29
|
+
onCascadeAccountResourceProgress?: (operationKey: string, event: ResourceEvent, region?: string) => void;
|
|
30
|
+
onCascadeAccountComplete?: (operationKey: string, success: boolean, error?: string, region?: string) => void;
|
|
31
|
+
onCascadeComplete?: (result: CascadeDeploymentResult) => void;
|
|
32
|
+
onSSOUrl?: (url: string | null) => void;
|
|
33
|
+
onAuthComplete?: (result: AwsAuthResult | null) => void;
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
verbose?: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** CloudFormation error pattern indicating a stack doesn't exist */
|
|
2
|
+
export declare const STACK_NOT_FOUND_PATTERN = "does not exist";
|
|
3
|
+
/** CDK error pattern indicating no stacks matched the provided name */
|
|
4
|
+
export declare const CDK_NO_STACKS_MATCH = "No stacks match the name(s)";
|
|
5
|
+
/** The canonical name for generated infrastructure files */
|
|
6
|
+
export declare const INFRASTRUCTURE_FILENAME = "infrastructure.ts";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** CloudFormation error pattern indicating a stack doesn't exist */
|
|
2
|
+
export const STACK_NOT_FOUND_PATTERN = "does not exist";
|
|
3
|
+
/** CDK error pattern indicating no stacks matched the provided name */
|
|
4
|
+
export const CDK_NO_STACKS_MATCH = "No stacks match the name(s)";
|
|
5
|
+
/** The canonical name for generated infrastructure files */
|
|
6
|
+
export const INFRASTRUCTURE_FILENAME = "infrastructure.ts";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS credentials for deployment. deploy-core never resolves these itself —
|
|
3
|
+
* the caller (CLI or worker) obtains credentials and injects them.
|
|
4
|
+
*
|
|
5
|
+
* CLI: resolves via SSO/OIDC profile authentication.
|
|
6
|
+
* Worker: resolves via OIDC JWT → STS AssumeRoleWithWebIdentity.
|
|
7
|
+
*/
|
|
8
|
+
export interface AwsCredentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
sessionToken?: string;
|
|
12
|
+
region: string;
|
|
13
|
+
accountId?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pre-fetched organisation identity for org deployments.
|
|
17
|
+
*
|
|
18
|
+
* CLI: omits this, provides apiClient instead (fetches from API).
|
|
19
|
+
* Worker: provides this from DB, omits apiClient.
|
|
20
|
+
*/
|
|
21
|
+
export interface DeployIdentity {
|
|
22
|
+
/** Fjall organisation ID (maps to what getIdentity() returns) */
|
|
23
|
+
fjallOrgId: string;
|
|
24
|
+
/** Plan tier — used for entitlement checks */
|
|
25
|
+
plan?: string;
|
|
26
|
+
/** Account limit from plan */
|
|
27
|
+
accountLimits?: number;
|
|
28
|
+
/** Current account count */
|
|
29
|
+
accountUsage?: number;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DeploymentContext } from "./DeploymentTypes.js";
|
|
2
|
+
import type { StackOutputsRecord } from "./cloudformation.js";
|
|
3
|
+
export type { StackOutput, StackOutputsRecord } from "./cloudformation.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extended deployment context for application deployments
|
|
6
|
+
*/
|
|
7
|
+
export interface ApplicationDeploymentContext extends Omit<DeploymentContext, "deployType"> {
|
|
8
|
+
deployType: "application" | "app";
|
|
9
|
+
appName: string;
|
|
10
|
+
path: string;
|
|
11
|
+
isManagedAccount?: boolean;
|
|
12
|
+
callerIdentity?: CallerIdentity;
|
|
13
|
+
stackOutputs?: StackOutputsRecord;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* AWS Caller Identity structure
|
|
17
|
+
*/
|
|
18
|
+
export interface CallerIdentity {
|
|
19
|
+
Account: string;
|
|
20
|
+
UserId: string;
|
|
21
|
+
Arn: string;
|
|
22
|
+
}
|
|
23
|
+
export type { ValidationResult } from "../validation.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StackOutputsRecord } from "./cloudformation.js";
|
|
2
|
+
import type { CallerIdentity } from "./DeploymentServiceTypes.js";
|
|
3
|
+
export interface DeploymentContext {
|
|
4
|
+
deployType: "application" | "organisation" | "platform" | "account";
|
|
5
|
+
target: string;
|
|
6
|
+
accountName?: string;
|
|
7
|
+
region?: string;
|
|
8
|
+
callerIdentity?: CallerIdentity;
|
|
9
|
+
stackOutputs?: StackOutputsRecord;
|
|
10
|
+
isManagedAccount?: boolean;
|
|
11
|
+
imageVersion?: string;
|
|
12
|
+
orgId?: string;
|
|
13
|
+
rootId?: string;
|
|
14
|
+
managementAccountId?: string;
|
|
15
|
+
ipamPoolId?: string;
|
|
16
|
+
fjallOrgId?: string;
|
|
17
|
+
orgConfig?: string;
|
|
18
|
+
options: {
|
|
19
|
+
verbose?: boolean;
|
|
20
|
+
infraOnly?: boolean;
|
|
21
|
+
};
|
|
22
|
+
path: string;
|
|
23
|
+
logPath?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface StepOutput {
|
|
26
|
+
message: string;
|
|
27
|
+
status?: "NO_CHANGES" | "SKIPPED";
|
|
28
|
+
details?: Record<string, unknown>;
|
|
29
|
+
}
|