@fjall/util 0.96.0 → 0.99.3
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/.minified +1 -1
- package/dist/Config.d.ts +1 -1
- package/dist/appPath.d.ts +29 -0
- package/dist/appPath.js +1 -0
- package/dist/constructMap.d.ts +9 -13
- package/dist/constructMap.js +1 -1
- package/dist/deriveContentHashTag.d.ts +34 -0
- package/dist/deriveContentHashTag.js +1 -0
- package/dist/docker/DockerCli.build.d.ts +42 -0
- package/dist/docker/DockerCli.build.js +1 -0
- package/dist/docker/DockerCli.d.ts +135 -0
- package/dist/docker/DockerCli.daemon.d.ts +24 -0
- package/dist/docker/DockerCli.daemon.js +1 -0
- package/dist/docker/DockerCli.js +1 -0
- package/dist/docker/DockerCli.registry.d.ts +19 -0
- package/dist/docker/DockerCli.registry.js +3 -0
- package/dist/docker/abortHelpers.d.ts +18 -0
- package/dist/docker/abortHelpers.js +1 -0
- package/dist/docker/buildxArgvBuilder.d.ts +15 -0
- package/dist/docker/buildxArgvBuilder.js +1 -0
- package/dist/docker/dockerCliConstants.d.ts +15 -0
- package/dist/docker/dockerCliConstants.js +1 -0
- package/dist/docker/dockerCliSchemas.d.ts +88 -0
- package/dist/docker/dockerCliSchemas.js +1 -0
- package/dist/docker/index.d.ts +10 -0
- package/dist/docker/index.js +1 -0
- package/dist/docker/metadataFileParser.d.ts +17 -0
- package/dist/docker/metadataFileParser.js +1 -0
- package/dist/docker/projectBuildxResult.d.ts +35 -0
- package/dist/docker/projectBuildxResult.js +1 -0
- package/dist/docker/rawjsonParser.d.ts +56 -0
- package/dist/docker/rawjsonParser.js +1 -0
- package/dist/docker/rawjsonToVertexEvent.d.ts +64 -0
- package/dist/docker/rawjsonToVertexEvent.js +1 -0
- package/dist/docker/result.d.ts +34 -0
- package/dist/docker/result.js +1 -0
- package/dist/errorUtils.d.ts +14 -4
- package/dist/findInfrastructurePaths.d.ts +62 -0
- package/dist/findInfrastructurePaths.js +1 -0
- package/dist/findRepoRoot.d.ts +12 -0
- package/dist/findRepoRoot.js +1 -0
- package/dist/fsHelpers.d.ts +15 -0
- package/dist/fsHelpers.js +1 -1
- package/dist/fsScan.d.ts +15 -0
- package/dist/fsScan.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -1
- package/dist/inferContainerFromCandidates.d.ts +23 -0
- package/dist/inferContainerFromCandidates.js +1 -0
- package/dist/manifest/index.d.ts +10 -0
- package/dist/manifest/index.js +1 -0
- package/dist/manifest/io.d.ts +60 -0
- package/dist/manifest/io.js +1 -0
- package/dist/manifest/schemas.d.ts +163 -0
- package/dist/manifest/schemas.js +1 -0
- package/dist/mcpProtocol/index.d.ts +362 -0
- package/dist/mcpProtocol/index.js +1 -0
- package/dist/migration/clickhouseSqlUsers.d.ts +72 -0
- package/dist/migration/clickhouseSqlUsers.js +1 -0
- package/dist/migration/constants.d.ts +34 -0
- package/dist/migration/constants.js +1 -0
- package/dist/migration/index.d.ts +3 -0
- package/dist/migration/index.js +1 -0
- package/dist/migration/pickLatestPrismaMigration.d.ts +10 -0
- package/dist/migration/pickLatestPrismaMigration.js +1 -0
- package/dist/reservedAppNames.d.ts +30 -0
- package/dist/reservedAppNames.js +1 -0
- package/dist/scanLocalRepository.d.ts +24 -0
- package/dist/scanLocalRepository.js +1 -0
- package/dist/scanTypes.d.ts +17 -0
- package/dist/scanTypes.js +0 -0
- package/dist/securityHelpers.d.ts +11 -1
- package/dist/securityHelpers.js +1 -1
- package/dist/tokenScopes.d.ts +11 -0
- package/dist/tokenScopes.js +1 -0
- package/package.json +43 -9
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure projection: `BuildxBuildResult` → consumer-facing
|
|
3
|
+
* `DockerBuildResultLike`. PR 1 (CLI adapter) and PR 2 (worker adapter)
|
|
4
|
+
* both call this single function so the deploy-core `DockerBuildResult`
|
|
5
|
+
* shape is produced symmetrically from either side; parity tests assert
|
|
6
|
+
* byte-identical output for matching inputs.
|
|
7
|
+
*
|
|
8
|
+
* `DockerBuildResultLike` is a structural mirror of deploy-core's
|
|
9
|
+
* `DockerBuildResult` (orchestration/dockerInterface.ts). Defined locally
|
|
10
|
+
* to keep `@fjall/util` at the root of the workspace dep graph — adapters
|
|
11
|
+
* upcast structurally at their boundary.
|
|
12
|
+
*
|
|
13
|
+
* Required digest source: `BuildxBuildResult.imageDigests` (one entry per
|
|
14
|
+
* pushed/loaded tag). When the primary tag has no digest in the map we
|
|
15
|
+
* fail with `metadata_missing_digest` per design § 3.1.1.
|
|
16
|
+
*/
|
|
17
|
+
import type { BuildxBuildResult, DockerCliError } from "./dockerCliSchemas.js";
|
|
18
|
+
import { type Result } from "./result.js";
|
|
19
|
+
export interface DockerBuildResultLike {
|
|
20
|
+
readonly imageUri: string;
|
|
21
|
+
readonly imageTag: string;
|
|
22
|
+
readonly services?: ReadonlyArray<{
|
|
23
|
+
name: string;
|
|
24
|
+
imageUri: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export interface ProjectBuildxResultArgs {
|
|
28
|
+
readonly result: BuildxBuildResult;
|
|
29
|
+
readonly primaryTag: string;
|
|
30
|
+
readonly services?: ReadonlyArray<{
|
|
31
|
+
name: string;
|
|
32
|
+
tag: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare function projectBuildxResult(args: ProjectBuildxResultArgs): Result<DockerBuildResultLike, DockerCliError>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{makeError as n}from"./DockerCli.js";import{failure as g,success as o}from"./result.js";function c(s){const e=s.lastIndexOf(":");return e===-1||s.lastIndexOf("/")>e?s:s.slice(e+1)}function l(s){const{result:e,primaryTag:t,services:a}=s;if(e.imageDigests[t]===void 0)return g(n("metadata_missing_digest",`Buildx result has no digest for primary tag ${t}`,{primaryTag:t,availableTags:Object.keys(e.imageDigests)}));const r=[];if(a!==void 0)for(const i of a){if(e.imageDigests[i.tag]===void 0)return g(n("metadata_missing_digest",`Buildx result has no digest for service ${i.name} tag ${i.tag}`,{service:i.name,tag:i.tag,availableTags:Object.keys(e.imageDigests)}));r.push({name:i.name,imageUri:i.tag})}return o({imageUri:t,imageTag:c(t),...r.length>0&&{services:r}})}export{l as projectBuildxResult};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensive line-by-line parser for `docker buildx build --progress=rawjson` stdout.
|
|
3
|
+
*
|
|
4
|
+
* Line contract: the caller is responsible for buffering subprocess stdout
|
|
5
|
+
* chunks into newline-delimited lines. This function consumes exactly one
|
|
6
|
+
* complete line per call and never returns partial parses.
|
|
7
|
+
*
|
|
8
|
+
* `RawjsonEnvelope` is a TS-typed structural shape, NOT a Zod schema.
|
|
9
|
+
* Buildx is the upstream producer and ships new fields between minor
|
|
10
|
+
* versions; a `.strict()` schema would reject valid output the moment
|
|
11
|
+
* a buildx upgrade lands. The external-API exemption from typescript-
|
|
12
|
+
* standards Pitfall 3 applies. Unknown keys are preserved on the
|
|
13
|
+
* `extra` record so downstream consumers and diagnostics can still see
|
|
14
|
+
* what was sent.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` only when the line is not parseable as JSON, or is a
|
|
17
|
+
* JSON value other than a non-array object (e.g. a top-level string,
|
|
18
|
+
* number, or array — none of which are valid rawjson envelopes).
|
|
19
|
+
*/
|
|
20
|
+
export interface RawjsonVertex {
|
|
21
|
+
readonly digest?: string;
|
|
22
|
+
readonly name?: string;
|
|
23
|
+
readonly started?: string;
|
|
24
|
+
readonly completed?: string;
|
|
25
|
+
readonly cached?: boolean;
|
|
26
|
+
readonly error?: string;
|
|
27
|
+
readonly inputs?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export interface RawjsonStatus {
|
|
30
|
+
readonly id?: string;
|
|
31
|
+
readonly vertex?: string;
|
|
32
|
+
readonly name?: string;
|
|
33
|
+
readonly current?: number;
|
|
34
|
+
readonly total?: number;
|
|
35
|
+
readonly started?: string;
|
|
36
|
+
readonly completed?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RawjsonLog {
|
|
39
|
+
readonly vertex?: string;
|
|
40
|
+
readonly stream?: number;
|
|
41
|
+
readonly data?: string;
|
|
42
|
+
readonly timestamp?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RawjsonWarning {
|
|
45
|
+
readonly vertex?: string;
|
|
46
|
+
readonly level?: string;
|
|
47
|
+
readonly short?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface RawjsonEnvelope {
|
|
50
|
+
readonly vertexes?: readonly RawjsonVertex[];
|
|
51
|
+
readonly statuses?: readonly RawjsonStatus[];
|
|
52
|
+
readonly logs?: readonly RawjsonLog[];
|
|
53
|
+
readonly warnings?: readonly RawjsonWarning[];
|
|
54
|
+
readonly extra: Readonly<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
export declare function parseRawjsonLine(line: string): RawjsonEnvelope | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=new Set(["vertexes","statuses","logs","warnings"]);function l(a){const e=a.trim();if(e==="")return null;let s;try{s=JSON.parse(e)}catch{return null}if(s===null||typeof s!="object"||Array.isArray(s))return null;const r=s,t={};for(const[n,i]of Object.entries(r))o.has(n)||(t[n]=i);return{...Array.isArray(r.vertexes)&&{vertexes:r.vertexes},...Array.isArray(r.statuses)&&{statuses:r.statuses},...Array.isArray(r.logs)&&{logs:r.logs},...Array.isArray(r.warnings)&&{warnings:r.warnings},extra:t}}export{l as parseRawjsonLine};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-contained adapter: `RawjsonEnvelope` → `RawjsonVertexEvent`.
|
|
3
|
+
*
|
|
4
|
+
* The output shape is OWNED by `@fjall/util/docker`. It does NOT import
|
|
5
|
+
* from `@fjall/cli` or any cli-side BuildKit decoder module — that bridge
|
|
6
|
+
* is a PR 2 concern. The shape below is structurally compatible with the
|
|
7
|
+
* cli-side `BuildKitStateManager` input (`BuildKitStatusResponse`); the
|
|
8
|
+
* PR 2 bridge maps these fields directly.
|
|
9
|
+
*
|
|
10
|
+
* Field mapping the PR 2 bridge will use:
|
|
11
|
+
* vertex.digest → BuildKitVertex.digest
|
|
12
|
+
* vertex.name → BuildKitVertex.name
|
|
13
|
+
* vertex.started → BuildKitVertex.started (timestamp)
|
|
14
|
+
* vertex.completed → BuildKitVertex.completed (timestamp)
|
|
15
|
+
* vertex.error → BuildKitVertex.error
|
|
16
|
+
* log.data → BuildKitVertexLog.data (base64-encoded; consumer decodes)
|
|
17
|
+
* log.stream → BuildKitVertexLog.stream (1=stdout, 2=stderr)
|
|
18
|
+
* status.id → BuildKitVertexStatus.id
|
|
19
|
+
* status.name → BuildKitVertexStatus.name
|
|
20
|
+
*
|
|
21
|
+
* Same external-API exemption as `rawjsonParser.ts`: this shape is
|
|
22
|
+
* structural-typed, not Zod-validated. Buildx ships new fields without
|
|
23
|
+
* notice; a `.strict()` schema would reject valid output.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` if the envelope has no extractable vertex/status/log/warning
|
|
26
|
+
* arrays (e.g. when `extra` contains buildx-private metadata only).
|
|
27
|
+
*/
|
|
28
|
+
import type { RawjsonEnvelope } from "./rawjsonParser.js";
|
|
29
|
+
export interface NormalisedVertex {
|
|
30
|
+
readonly digest: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly started?: string;
|
|
33
|
+
readonly completed?: string;
|
|
34
|
+
readonly cached?: boolean;
|
|
35
|
+
readonly error?: string;
|
|
36
|
+
readonly inputs?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export interface NormalisedStatus {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly vertex: string;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly current: number;
|
|
43
|
+
readonly total?: number;
|
|
44
|
+
readonly started?: string;
|
|
45
|
+
readonly completed?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface NormalisedLog {
|
|
48
|
+
readonly vertex: string;
|
|
49
|
+
readonly stream: "stdout" | "stderr";
|
|
50
|
+
readonly data: string;
|
|
51
|
+
readonly timestamp?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface NormalisedWarning {
|
|
54
|
+
readonly vertex: string;
|
|
55
|
+
readonly level: string;
|
|
56
|
+
readonly short: string;
|
|
57
|
+
}
|
|
58
|
+
export interface RawjsonVertexEvent {
|
|
59
|
+
readonly vertexes: readonly NormalisedVertex[];
|
|
60
|
+
readonly statuses: readonly NormalisedStatus[];
|
|
61
|
+
readonly logs: readonly NormalisedLog[];
|
|
62
|
+
readonly warnings: readonly NormalisedWarning[];
|
|
63
|
+
}
|
|
64
|
+
export declare function rawjsonToVertexEvent(envelope: RawjsonEnvelope): RawjsonVertexEvent | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(t){return typeof t.digest!="string"||t.digest===""||typeof t.name!="string"||t.name===""?null:{digest:t.digest,name:t.name,...typeof t.started=="string"&&{started:t.started},...typeof t.completed=="string"&&{completed:t.completed},...typeof t.cached=="boolean"&&{cached:t.cached},...typeof t.error=="string"&&{error:t.error},...Array.isArray(t.inputs)&&{inputs:t.inputs}}}function f(t){return typeof t.id!="string"||t.id===""||typeof t.vertex!="string"||t.vertex===""||typeof t.name!="string"||typeof t.current!="number"?null:{id:t.id,vertex:t.vertex,name:t.name,current:t.current,...typeof t.total=="number"&&{total:t.total},...typeof t.started=="string"&&{started:t.started},...typeof t.completed=="string"&&{completed:t.completed}}}function l(t){if(typeof t.vertex!="string"||t.vertex===""||typeof t.data!="string")return null;const n=t.stream===2?"stderr":"stdout";return{vertex:t.vertex,stream:n,data:t.data,...typeof t.timestamp=="string"&&{timestamp:t.timestamp}}}function a(t){return typeof t.vertex!="string"||t.vertex===""||typeof t.short!="string"?null:{vertex:t.vertex,level:typeof t.level=="string"?t.level:"warning",short:t.short}}function c(t){const n=[];for(const r of t.vertexes??[]){const e=u(r);e!==null&&n.push(e)}const o=[];for(const r of t.statuses??[]){const e=f(r);e!==null&&o.push(e)}const s=[];for(const r of t.logs??[]){const e=l(r);e!==null&&s.push(e)}const i=[];for(const r of t.warnings??[]){const e=a(r);e!==null&&i.push(e)}return n.length===0&&o.length===0&&s.length===0&&i.length===0?null:{vertexes:n,statuses:o,logs:s,warnings:i}}export{c as rawjsonToVertexEvent};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local `Result<T, E>` for `@fjall/util/docker`.
|
|
3
|
+
*
|
|
4
|
+
* The canonical Result type lives in `@fjall/generator/src/types/Result.ts`
|
|
5
|
+
* and is re-exported through `@fjall/deploy-core`. We do NOT import either
|
|
6
|
+
* here: `@fjall/util` has no `@fjall/*` dependencies (it is the root of the
|
|
7
|
+
* workspace dep graph) and adding one would create a circular workspace
|
|
8
|
+
* dependency (generator already depends on util).
|
|
9
|
+
*
|
|
10
|
+
* The shape below is structurally identical to the canonical Result, so
|
|
11
|
+
* adapters in `@fjall/cli` (PR 2) and `webapp` (PR 1) consume values
|
|
12
|
+
* produced here and pass them through to deploy-core consumers without
|
|
13
|
+
* conversion. If the canonical Result shape ever changes, the bridge will
|
|
14
|
+
* fail to typecheck — that is the drift signal.
|
|
15
|
+
*
|
|
16
|
+
* Reference: `fjall/generator/src/types/Result.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export type Result<T, E = Error> = {
|
|
19
|
+
success: true;
|
|
20
|
+
data: T;
|
|
21
|
+
} | {
|
|
22
|
+
success: false;
|
|
23
|
+
error: E;
|
|
24
|
+
};
|
|
25
|
+
export declare function isSuccess<T, E>(result: Result<T, E>): result is {
|
|
26
|
+
success: true;
|
|
27
|
+
data: T;
|
|
28
|
+
};
|
|
29
|
+
export declare function isFailure<T, E>(result: Result<T, E>): result is {
|
|
30
|
+
success: false;
|
|
31
|
+
error: E;
|
|
32
|
+
};
|
|
33
|
+
export declare function success<T>(data: T): Result<T, never>;
|
|
34
|
+
export declare function failure<E>(error: E): Result<never, E>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(s){return s.success===!0}function u(s){return s.success===!1}function c(s){return{success:!0,data:s}}function r(s){return{success:!1,error:s}}export{r as failure,u as isFailure,e as isSuccess,c as success};
|
package/dist/errorUtils.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Error extractor helpers consumed by the
|
|
3
|
+
* `fjall/mask-error-message-at-boundary` ESLint rule by name. Renaming any
|
|
4
|
+
* of `getErrorMessage`, `getErrorStack`, or `formatErrorString` requires a
|
|
5
|
+
* matching update in `cli/eslint-rules/mask-error-message-at-boundary.js`
|
|
6
|
+
* (`ERROR_EXTRACTOR_CALLEES`).
|
|
7
|
+
*
|
|
8
|
+
* Normalise an unknown thrown value into an Error instance.
|
|
3
9
|
*/
|
|
4
10
|
export declare function normaliseError(error: unknown): Error;
|
|
5
11
|
/**
|
|
6
|
-
* Safely extract error message from any error type
|
|
12
|
+
* Safely extract error message from any error type. Extractor helper —
|
|
13
|
+
* see file-header note. Result MUST be wrapped in `maskSensitiveOutput()`
|
|
14
|
+
* before reaching any logger / state setter / persistence / callback sink.
|
|
7
15
|
*/
|
|
8
16
|
export declare function getErrorMessage(error: unknown): string;
|
|
9
17
|
/**
|
|
@@ -15,10 +23,12 @@ export declare function hasErrorCode(error: unknown, code: string): boolean;
|
|
|
15
23
|
*/
|
|
16
24
|
export declare function getErrorCode(error: unknown): string | undefined;
|
|
17
25
|
/**
|
|
18
|
-
* Safely extract the stack trace from an unknown thrown value
|
|
26
|
+
* Safely extract the stack trace from an unknown thrown value. Extractor
|
|
27
|
+
* helper — must be `maskSensitiveOutput()`-wrapped at every sink.
|
|
19
28
|
*/
|
|
20
29
|
export declare function getErrorStack(error: unknown): string | undefined;
|
|
21
30
|
/**
|
|
22
|
-
* Create a formatted error string with optional code prefix
|
|
31
|
+
* Create a formatted error string with optional code prefix. Extractor
|
|
32
|
+
* helper — must be `maskSensitiveOutput()`-wrapped at every sink.
|
|
23
33
|
*/
|
|
24
34
|
export declare function formatErrorString(error: unknown): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared marker-path resolver for cross-system scan parity.
|
|
3
|
+
*
|
|
4
|
+
* Both the CLI's `scanLocalRepository` (filesystem walker) and the webapp's
|
|
5
|
+
* `scanInfrastructureFiles` (GitHub tree API) need the same answer to:
|
|
6
|
+
*
|
|
7
|
+
* "Given a path, is it an `infrastructure.ts` inside a `fjall/` boundary,
|
|
8
|
+
* and if so, what is its `configPath` + `boundaryPath`?"
|
|
9
|
+
*
|
|
10
|
+
* This module is the single source of truth for that resolution. Each
|
|
11
|
+
* scanner is responsible for its own I/O (walk filesystem / list GitHub
|
|
12
|
+
* tree); both hand the resulting iterable of `{ path }` entries to
|
|
13
|
+
* `findInfrastructurePaths`, optionally pre-filtered via the options below.
|
|
14
|
+
*
|
|
15
|
+
* No I/O. Pure path classification. The structural contract is the function
|
|
16
|
+
* signature; review-time agreement is not load-bearing.
|
|
17
|
+
*
|
|
18
|
+
* See `aiDocs/patterns/fjall-marker-convention-pattern.md` for the convention
|
|
19
|
+
* and `aiDocs/decisions/2026-05-11-scan-parity-contract-vs-structural.md`
|
|
20
|
+
* for the rationale behind extracting this module.
|
|
21
|
+
*/
|
|
22
|
+
import type { ScanPath } from "./scanTypes.js";
|
|
23
|
+
export declare const MARKER_DIRECTORY = "fjall";
|
|
24
|
+
export interface MarkerEntry {
|
|
25
|
+
/** POSIX-style path relative to the repo root. */
|
|
26
|
+
path: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FindInfrastructurePathsOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Reject entries whose path starts with any of these prefixes. Use for
|
|
31
|
+
* exclusion sets that span subtrees (e.g. `"node_modules/"`,
|
|
32
|
+
* `".fjall/history/"`). Each prefix MUST end with `/` to keep the match
|
|
33
|
+
* directory-aware.
|
|
34
|
+
*/
|
|
35
|
+
excludedPathPrefixes?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Reject entries that include any of these path segments anywhere in their
|
|
38
|
+
* path (excluding the filename segment). Use for build-output directories
|
|
39
|
+
* the consumer's I/O layer cannot prune at walk time (e.g. `"dist"`,
|
|
40
|
+
* `".turbo"`). Match is segment-exact.
|
|
41
|
+
*/
|
|
42
|
+
excludedPathSegments?: ReadonlySet<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve an iterable of repo-relative paths to the subset that are
|
|
46
|
+
* `infrastructure.ts` files inside a `fjall/` boundary. Returns one
|
|
47
|
+
* `{ configPath, boundaryPath }` per match.
|
|
48
|
+
*
|
|
49
|
+
* Boundary rule: the **leftmost** path segment named exactly `fjall` wins.
|
|
50
|
+
* Nested `fjall/` folders inside the boundary do not redefine it.
|
|
51
|
+
*
|
|
52
|
+
* The caller is responsible for any cap or ranking; this function applies
|
|
53
|
+
* neither. Exclusion via `options` is OPTIONAL — consumers that pre-filter
|
|
54
|
+
* at their I/O layer may omit it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findInfrastructurePaths(entries: Iterable<MarkerEntry>, options?: FindInfrastructurePathsOptions): ScanPath[];
|
|
57
|
+
export declare function isInfrastructureFile(path: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Return the path to the leftmost `fjall/` ancestor, or `null` if the path
|
|
60
|
+
* does not cross a `fjall/` boundary. The result excludes the file segment.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findBoundaryPath(path: string): string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const i="infrastructure.ts",f="fjall";function x(t,e){const n=e?.excludedPathPrefixes,r=e?.excludedPathSegments,s=[];for(const u of t){if(!c(u.path)||n&&h(u.path,n)||r&&d(u.path,r))continue;const o=a(u.path);o!==null&&s.push({configPath:l(u.path),boundaryPath:o})}return s}function c(t){return t===i||t.endsWith(`/${i}`)}function a(t){const e=t.split("/");for(let n=0;n<e.length-1;n++)if(e[n]===f)return e.slice(0,n+1).join("/");return null}function l(t){return t.slice(0,-`/${i}`.length)}function h(t,e){for(const n of e)if(t.startsWith(n))return!0;return!1}function d(t,e){const n=t.split("/");for(let r=0;r<n.length-1;r++)if(e.has(n[r]))return!0;return!1}export{f as MARKER_DIRECTORY,a as findBoundaryPath,x as findInfrastructurePaths,c as isInfrastructureFile};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk parents from `cwd` looking for the nearest enclosing repository
|
|
3
|
+
* root, identified by either a `.git/` directory or a `fjall-config.json`
|
|
4
|
+
* file. Returns the absolute path of the first directory matching either
|
|
5
|
+
* marker, or falls back to `cwd` when no ancestor matches.
|
|
6
|
+
*
|
|
7
|
+
* Used by the CLI scaffold pre-flight so `scanLocalRepository` always
|
|
8
|
+
* walks from the repo root: a `fjall create app` run from
|
|
9
|
+
* `services/api/` still discovers existing markers under
|
|
10
|
+
* `services/worker/fjall/`, not only the descendants of `cwd`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findRepoRoot(cwd: string): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{stat as a}from"fs/promises";import{dirname as f,join as c,parse as m,resolve as o}from"path";const u=[".git","fjall-config.json"];async function h(r){let t=o(r);const{root:n}=m(t);for(;;){for(const s of u){const i=c(t,s);if(await a(i).then(()=>!0,()=>!1))return t}if(t===n)return o(r);const e=f(t);if(e===t)return o(r);t=e}}export{h as findRepoRoot};
|
package/dist/fsHelpers.d.ts
CHANGED
|
@@ -2,3 +2,18 @@
|
|
|
2
2
|
* Async check for file existence. Replaces blocking existsSync().
|
|
3
3
|
*/
|
|
4
4
|
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
5
|
+
export interface AtomicWriteOptions {
|
|
6
|
+
/** File mode for the destination file. Default 0o644. */
|
|
7
|
+
mode?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Called when tmp-file cleanup fails after a write/rename error.
|
|
10
|
+
* Receives the cleanup error so the caller can log it. Default: ignore.
|
|
11
|
+
*/
|
|
12
|
+
onCleanupError?: (err: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Atomically write a file via mkdtemp-style tmp + rename. Crash-safe:
|
|
16
|
+
* the destination either contains the previous bytes or the new bytes,
|
|
17
|
+
* never a partial write.
|
|
18
|
+
*/
|
|
19
|
+
export declare function atomicWrite(filePath: string, contents: string | Uint8Array, options?: AtomicWriteOptions): void;
|
package/dist/fsHelpers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{access as
|
|
1
|
+
import{access as i,constants as m}from"fs/promises";import{renameSync as s,unlinkSync as f,writeFileSync as p}from"node:fs";import{randomBytes as u}from"node:crypto";async function d(t){try{return await i(t,m.F_OK),!0}catch(r){if(r instanceof Error&&"code"in r&&r.code==="ENOENT")return!1;throw r}}function l(t,r,n={}){const o=`${t}.${u(6).toString("hex")}.tmp`,e=n.mode??420;try{p(o,r,{mode:e}),s(o,t)}catch(c){try{f(o)}catch(a){n.onCleanupError?.(a)}throw c}}export{l as atomicWrite,d as fileExists};
|
package/dist/fsScan.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-bound `@fjall/util` subpath barrel.
|
|
3
|
+
*
|
|
4
|
+
* The helpers here depend on `fs/promises`, which MUST NOT reach webapp
|
|
5
|
+
* client bundles. The subpath split (vs the top-level `@fjall/util`
|
|
6
|
+
* barrel) enforces that boundary: the top-level barrel stays pure and
|
|
7
|
+
* tree-shakable into the browser; anything node-only ships here.
|
|
8
|
+
*
|
|
9
|
+
* Consumers:
|
|
10
|
+
* - CLI (`createAppHandler`) → both `scanLocalRepository` + `findRepoRoot`
|
|
11
|
+
* - Webapp server-side code may import here too; webapp client code MUST NOT.
|
|
12
|
+
*/
|
|
13
|
+
export { scanLocalRepository, type ScanLocalRepositoryResult } from "./scanLocalRepository.js";
|
|
14
|
+
export { findRepoRoot } from "./findRepoRoot.js";
|
|
15
|
+
export { type ScanPath } from "./scanTypes.js";
|
package/dist/fsScan.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{scanLocalRepository as e}from"./scanLocalRepository.js";import{findRepoRoot as t}from"./findRepoRoot.js";export{t as findRepoRoot,e as scanLocalRepository};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,12 @@ export { STANDARD_ENVIRONMENTS, STRUCTURAL_ENVIRONMENTS, type StandardEnvironmen
|
|
|
8
8
|
export { RESOURCE_CATEGORIES, type ResourceCategory, categoriseResource, getExpectedDuration, getFriendlyResourceType } from "./resourceCategorisation.js";
|
|
9
9
|
export { parseGitRemoteUrl, type GitProvider, type ParsedGitRemote } from "./gitRemoteParser.js";
|
|
10
10
|
export { abbreviateRegion } from "./regions.js";
|
|
11
|
+
export { SCOPE_VALUES, type TokenScope } from "./tokenScopes.js";
|
|
11
12
|
export { deriveRegionsFromOrgConfig, deriveTargets, deriveAllTargets, findTarget, generateTargetName, type OrgConfigRegions, type TargetAccount, type DerivedTarget } from "./targets.js";
|
|
13
|
+
export { buildAppConfigPath } from "./appPath.js";
|
|
14
|
+
export { type ScanPath } from "./scanTypes.js";
|
|
15
|
+
export { findInfrastructurePaths, findBoundaryPath, isInfrastructureFile, type MarkerEntry, type FindInfrastructurePathsOptions } from "./findInfrastructurePaths.js";
|
|
16
|
+
export { inferContainerFromCandidates } from "./inferContainerFromCandidates.js";
|
|
17
|
+
export { RESERVED_APP_NAMES, type ReservedAppName, isReservedAppName } from "./reservedAppNames.js";
|
|
18
|
+
export { deriveContentHashTag } from "./deriveContentHashTag.js";
|
|
19
|
+
export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, PRISMA_MIGRATION_DIR_RE } from "./migration/constants.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DNS_APEX as o,getDomainExportNames as t}from"./domainExports.js";import{toPascalCase as
|
|
1
|
+
import{DNS_APEX as o,getDomainExportNames as t}from"./domainExports.js";import{toPascalCase as E,toKebab as i,toValidDatabaseName as n,toScreamingSnake as m,capitalise as s,getSafeZoneName as p}from"./caseConversion.js";import{normaliseError as S,getErrorMessage as N,hasErrorCode as R,getErrorCode as g,getErrorStack as _,formatErrorString as A}from"./errorUtils.js";import{singleton as T}from"./singleton.js";import{DANGEROUS_ENV_VARS as l,filterDangerousEnvVars as C,maskSensitiveOutput as O,parseShellArgs as I}from"./securityHelpers.js";import{sleep as P}from"./sleep.js";import{STANDARD_ENVIRONMENTS as D,STRUCTURAL_ENVIRONMENTS as M,isValidEnvironment as c,ENVIRONMENT_LABELS as v,getEnvironmentLabel as b}from"./environments.js";import{RESOURCE_CATEGORIES as L,categoriseResource as U,getExpectedDuration as F,getFriendlyResourceType as G}from"./resourceCategorisation.js";import{parseGitRemoteUrl as X}from"./gitRemoteParser.js";import{abbreviateRegion as y}from"./regions.js";import{SCOPE_VALUES as K}from"./tokenScopes.js";import{deriveRegionsFromOrgConfig as j,deriveTargets as q,deriveAllTargets as w,findTarget as z,generateTargetName as J}from"./targets.js";import{buildAppConfigPath as W}from"./appPath.js";import{findInfrastructurePaths as $,findBoundaryPath as ee,isInfrastructureFile as re}from"./findInfrastructurePaths.js";import{inferContainerFromCandidates as te}from"./inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Ee,isReservedAppName as ie}from"./reservedAppNames.js";import{deriveContentHashTag as me}from"./deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as pe,EXPECTED_SCHEMA_VERSION_ENV as fe,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Se,PRISMA_MIGRATION_DIR_RE as Ne}from"./migration/constants.js";export{l as DANGEROUS_ENV_VARS,o as DNS_APEX,v as ENVIRONMENT_LABELS,fe as EXPECTED_SCHEMA_VERSION_ENV,Se as EXPECTED_SCHEMA_VERSION_TOOL_ENV,pe as MIGRATION_SNAPSHOT_NAME_PREFIX,Ne as PRISMA_MIGRATION_DIR_RE,Ee as RESERVED_APP_NAMES,L as RESOURCE_CATEGORIES,K as SCOPE_VALUES,D as STANDARD_ENVIRONMENTS,M as STRUCTURAL_ENVIRONMENTS,y as abbreviateRegion,W as buildAppConfigPath,s as capitalise,U as categoriseResource,w as deriveAllTargets,me as deriveContentHashTag,j as deriveRegionsFromOrgConfig,q as deriveTargets,C as filterDangerousEnvVars,ee as findBoundaryPath,$ as findInfrastructurePaths,z as findTarget,A as formatErrorString,J as generateTargetName,t as getDomainExportNames,b as getEnvironmentLabel,g as getErrorCode,N as getErrorMessage,_ as getErrorStack,F as getExpectedDuration,G as getFriendlyResourceType,p as getSafeZoneName,R as hasErrorCode,te as inferContainerFromCandidates,re as isInfrastructureFile,ie as isReservedAppName,c as isValidEnvironment,O as maskSensitiveOutput,S as normaliseError,X as parseGitRemoteUrl,I as parseShellArgs,T as singleton,P as sleep,i as toKebab,E as toPascalCase,m as toScreamingSnake,n as toValidDatabaseName};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure inference of the monorepo container directory from a list of
|
|
3
|
+
* scan paths.
|
|
4
|
+
*
|
|
5
|
+
* Each `ScanPath` carries a `boundaryPath` ending in the `fjall` marker
|
|
6
|
+
* segment. Stripping that segment yields the container — the directory
|
|
7
|
+
* between repo root and the `fjall/` boundary. For multiple paths, returns
|
|
8
|
+
* the longest common ancestor.
|
|
9
|
+
*
|
|
10
|
+
* Edge cases:
|
|
11
|
+
* - empty array → `null`
|
|
12
|
+
* - boundary at repo root (`fjall`) → `null`
|
|
13
|
+
* - single nested boundary → its parent directory
|
|
14
|
+
* - two with a common ancestor → the ancestor
|
|
15
|
+
* - two with no common ancestor → `null`
|
|
16
|
+
*
|
|
17
|
+
* Pure function — no I/O. Used by both the CLI scaffold pre-flight
|
|
18
|
+
* (`scanLocalRepository`) and the webapp scan endpoint
|
|
19
|
+
* (`scanInfrastructureFiles`) to suggest a default `--container` for new
|
|
20
|
+
* apps.
|
|
21
|
+
*/
|
|
22
|
+
import type { ScanPath } from "./scanTypes.js";
|
|
23
|
+
export declare function inferContainerFromCandidates(paths: readonly ScanPath[]): string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MARKER_DIRECTORY as u}from"./findInfrastructurePaths.js";function h(t){if(t.length===0)return null;const n=[];for(const e of t){const o=f(e.boundaryPath);if(o===null)return null;n.push(o)}const r=c(n);return r.length===0?null:r.join("/")}function f(t){const n=t.split("/").filter(e=>e.length>0);if(n.length===0||n[n.length-1]!==u)return null;const r=n.slice(0,n.length-1);return r.length===0?null:r}function c(t){if(t.length===0)return[];const[n,...r]=t,e=[];for(let o=0;o<n.length;o+=1){const l=n[o];for(const i of r)if(i[o]!==l)return e;e.push(l)}return e}export{h as inferContainerFromCandidates};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export for `@fjall/util/manifest`.
|
|
3
|
+
*
|
|
4
|
+
* NOTE on purity: this barrel imports the I/O module, which depends on
|
|
5
|
+
* `fs/promises`. Consumers that must remain Node-free (the generator's
|
|
6
|
+
* pure CDK synth path) MUST import directly from `./schemas` instead of
|
|
7
|
+
* the barrel — the package.json `exports` map exposes both subpaths.
|
|
8
|
+
*/
|
|
9
|
+
export { DockerBuildSchema, DockerBuildPartialSchema, mergeDockerBuild, ManifestServiceSchema, ManifestPatternSchema, ManifestEcrSchema, ManifestLambdaSchema, ManifestStackHashSchema, ResourceMapEntrySchema, FjallManifestSchema, FJALL_MANIFEST_FILENAME, MANIFEST_SCHEMA_VERSION, type DockerBuild, type DockerBuildPartial, type ManifestService, type ManifestPattern, type ManifestEcr, type ManifestLambda, type ManifestStackHash, type ResourceMapEntry, type FjallManifest } from "./schemas.js";
|
|
10
|
+
export { getManifestFilePath, readManifestFile, writeManifestFile, createEmptyManifest, readConstructMap, parseDockerServicesFromManifest, type ManifestDockerService } from "./io.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DockerBuildSchema as t,DockerBuildPartialSchema as r,mergeDockerBuild as i,ManifestServiceSchema as c,ManifestPatternSchema as M,ManifestEcrSchema as s,ManifestLambdaSchema as S,ManifestStackHashSchema as m,ResourceMapEntrySchema as n,FjallManifestSchema as f,FJALL_MANIFEST_FILENAME as h,MANIFEST_SCHEMA_VERSION as o}from"./schemas.js";import{getManifestFilePath as E,readManifestFile as F,writeManifestFile as d,createEmptyManifest as p,readConstructMap as k,parseDockerServicesFromManifest as u}from"./io.js";export{r as DockerBuildPartialSchema,t as DockerBuildSchema,h as FJALL_MANIFEST_FILENAME,f as FjallManifestSchema,o as MANIFEST_SCHEMA_VERSION,s as ManifestEcrSchema,S as ManifestLambdaSchema,M as ManifestPatternSchema,c as ManifestServiceSchema,m as ManifestStackHashSchema,n as ResourceMapEntrySchema,p as createEmptyManifest,E as getManifestFilePath,i as mergeDockerBuild,u as parseDockerServicesFromManifest,k as readConstructMap,F as readManifestFile,d as writeManifestFile};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only I/O helpers for the Fjall manifest.
|
|
3
|
+
*
|
|
4
|
+
* This module imports `fs/promises` and `path` and is therefore Node-only.
|
|
5
|
+
* The pure schema/types live in the sibling `./schemas` module — consumers
|
|
6
|
+
* that need the docker-shape types without I/O (e.g. the generator) MUST
|
|
7
|
+
* import from `./schemas` directly.
|
|
8
|
+
*/
|
|
9
|
+
import { type DockerBuild, type FjallManifest, type ResourceMapEntry } from "./schemas.js";
|
|
10
|
+
/**
|
|
11
|
+
* Get the manifest file path for a cdk.out directory.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getManifestFilePath(cdkOutPath: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Read manifest file from cdk.out directory.
|
|
16
|
+
* Returns null if file doesn't exist or is corrupt.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readManifestFile(cdkOutPath: string): Promise<FjallManifest | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Write manifest file atomically using temp file + rename pattern.
|
|
21
|
+
*/
|
|
22
|
+
export declare function writeManifestFile(cdkOutPath: string, manifest: FjallManifest): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Create an empty manifest structure.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createEmptyManifest(appName: string): FjallManifest;
|
|
27
|
+
/**
|
|
28
|
+
* Read the construct map from the fjall-manifest.json file.
|
|
29
|
+
* Returns an empty map if the file doesn't exist, is malformed,
|
|
30
|
+
* or has no resourceMap section. Never throws.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readConstructMap(cdkOutPath: string): Promise<Map<string, ResourceMapEntry>>;
|
|
33
|
+
/**
|
|
34
|
+
* A service entry from the manifest with its `docker` block guaranteed present.
|
|
35
|
+
*
|
|
36
|
+
* Used by deploy-core to gate Docker pre-build steps. Entries without a
|
|
37
|
+
* `docker` block (e.g. lambda-only manifests, or future role types that
|
|
38
|
+
* carry no Dockerfile) are filtered out — the deploy-core call sites only
|
|
39
|
+
* care about the build set, so the parser narrows here.
|
|
40
|
+
*/
|
|
41
|
+
export interface ManifestDockerService {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly docker: DockerBuild;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extract services with a Docker build configuration from the Fjall manifest.
|
|
47
|
+
*
|
|
48
|
+
* The manifest is the authoritative post-synth source for Docker
|
|
49
|
+
* configuration — `docker.path` may be absolute (cross-repo monorepo
|
|
50
|
+
* layouts) or relative to the app root, and a filesystem-only check
|
|
51
|
+
* cannot see absolute paths outside the app root.
|
|
52
|
+
*
|
|
53
|
+
* Returns an empty array if the manifest does not exist, cannot be parsed,
|
|
54
|
+
* or contains no Docker services. Never throws.
|
|
55
|
+
*
|
|
56
|
+
* Reads synchronously because deploy-core's call sites are inside synchronous
|
|
57
|
+
* orchestration loops; the file is small and sealed by CDK synth ahead of
|
|
58
|
+
* the deploy phase.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseDockerServicesFromManifest(cdkOutPath: string): ManifestDockerService[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as l,writeFile as m,unlink as h,rename as g,mkdir as M}from"fs/promises";import{readFileSync as y}from"fs";import{dirname as F,join as f}from"path";import{logger as s}from"../logger.js";import{fileExists as w}from"../fsHelpers.js";import{getErrorMessage as u}from"../errorUtils.js";import{recordToConstructMap as S}from"../constructMap.js";import{DockerBuildSchema as x,FjallManifestSchema as k,FJALL_MANIFEST_FILENAME as d,MANIFEST_SCHEMA_VERSION as A}from"./schemas.js";function p(e){return f(e,d)}async function E(e){const t=p(e);if(!await w(t))return null;try{const r=await l(t,"utf-8"),n=JSON.parse(r),a=k.safeParse(n);return a.success||s.debug("FjallManifest","Manifest validation failed",{path:t,errors:a.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`)}),a.success?a.data:null}catch(r){return s.debug("FjallManifest","Failed to read manifest file",{path:t,error:u(r)}),null}}async function T(e,t){const r=p(e),n=`${r}.${Date.now()}.tmp`;await M(F(r),{recursive:!0});try{await m(n,JSON.stringify(t,null,2),"utf-8"),await g(n,r)}catch(a){try{await h(n)}catch(i){s.debug("FjallManifest","Temp file cleanup failed (non-fatal)",{path:n,error:u(i)})}throw a}}function _(e){return{version:A,generatedAt:new Date().toISOString(),appName:e,services:[],lambdas:[],stacks:{}}}async function $(e){const t=await E(e);return t?.resourceMap?S(t.resourceMap):new Map}function o(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function j(e){if(!o(e)||typeof e.path!="string"||e.path.length===0)return;const t=typeof e.context=="string"&&e.context.length>0?e.context:void 0,r=typeof e.target=="string"&&e.target.length>0?e.target:void 0,n={path:e.path,...t!==void 0&&{context:t},...r!==void 0&&{target:r}},a=x.safeParse(n);return a.success?a.data:void 0}function L(e){const t=f(e,d);let r;try{r=y(t,"utf-8")}catch{return s.debug("FjallManifest","Manifest file not readable \u2014 no Docker services extracted",{path:t}),[]}let n;try{n=JSON.parse(r)}catch{return s.debug("FjallManifest","Manifest is not valid JSON \u2014 no Docker services extracted",{path:t}),[]}if(!o(n)||!Array.isArray(n.services))return[];const a=[];for(const i of n.services){if(!o(i)||typeof i.name!="string")continue;const c=j(i.docker);c!==void 0&&a.push({name:i.name,docker:c})}return a}export{_ as createEmptyManifest,p as getManifestFilePath,L as parseDockerServicesFromManifest,$ as readConstructMap,E as readManifestFile,T as writeManifestFile};
|