@untangle-code/context-artifacts 0.0.0-stage → 0.1.1
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 +661 -0
- package/NOTICE +90 -0
- package/README.md +247 -2
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/adapters/asyncapi.d.ts +20 -0
- package/dist/adapters/asyncapi.d.ts.map +1 -0
- package/dist/adapters/asyncapi.js +95 -0
- package/dist/adapters/asyncapi.js.map +1 -0
- package/dist/adapters/docker.d.ts +43 -0
- package/dist/adapters/docker.d.ts.map +1 -0
- package/dist/adapters/docker.js +97 -0
- package/dist/adapters/docker.js.map +1 -0
- package/dist/adapters/kubernetes.d.ts +22 -0
- package/dist/adapters/kubernetes.d.ts.map +1 -0
- package/dist/adapters/kubernetes.js +56 -0
- package/dist/adapters/kubernetes.js.map +1 -0
- package/dist/adapters/markdown.d.ts +35 -0
- package/dist/adapters/markdown.d.ts.map +1 -0
- package/dist/adapters/markdown.js +36 -0
- package/dist/adapters/markdown.js.map +1 -0
- package/dist/adapters/openapi.d.ts +21 -0
- package/dist/adapters/openapi.d.ts.map +1 -0
- package/dist/adapters/openapi.js +101 -0
- package/dist/adapters/openapi.js.map +1 -0
- package/dist/adapters/protobuf.d.ts +34 -0
- package/dist/adapters/protobuf.d.ts.map +1 -0
- package/dist/adapters/protobuf.js +221 -0
- package/dist/adapters/protobuf.js.map +1 -0
- package/dist/adapters/shared.d.ts +65 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +95 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/sql.d.ts +104 -0
- package/dist/adapters/sql.d.ts.map +1 -0
- package/dist/adapters/sql.js +427 -0
- package/dist/adapters/sql.js.map +1 -0
- package/dist/adapters/terraform.d.ts +40 -0
- package/dist/adapters/terraform.d.ts.map +1 -0
- package/dist/adapters/terraform.js +186 -0
- package/dist/adapters/terraform.js.map +1 -0
- package/dist/adapters/text.d.ts +24 -0
- package/dist/adapters/text.d.ts.map +1 -0
- package/dist/adapters/text.js +31 -0
- package/dist/adapters/text.js.map +1 -0
- package/dist/config-validation.d.ts +18 -0
- package/dist/config-validation.d.ts.map +1 -0
- package/dist/config-validation.js +30 -0
- package/dist/config-validation.js.map +1 -0
- package/dist/coverage.d.ts +114 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +0 -0
- package/dist/coverage.js.map +1 -0
- package/dist/ids.d.ts +12 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +42 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +51 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +86 -0
- package/dist/registry.js.map +1 -0
- package/dist/staleness.d.ts +44 -0
- package/dist/staleness.d.ts.map +1 -0
- package/dist/staleness.js +35 -0
- package/dist/staleness.js.map +1 -0
- package/dist/text/delimiter-balance.d.ts +95 -0
- package/dist/text/delimiter-balance.d.ts.map +1 -0
- package/dist/text/delimiter-balance.js +209 -0
- package/dist/text/delimiter-balance.js.map +1 -0
- package/dist/text/line-index.d.ts +61 -0
- package/dist/text/line-index.d.ts.map +1 -0
- package/dist/text/line-index.js +75 -0
- package/dist/text/line-index.js.map +1 -0
- package/dist/text/yaml-lite.d.ts +73 -0
- package/dist/text/yaml-lite.d.ts.map +1 -0
- package/dist/text/yaml-lite.js +486 -0
- package/dist/text/yaml-lite.js.map +1 -0
- package/package.json +61 -3
- package/src/adapters/__golden__/adr-0001-sections.json +68 -0
- package/src/adapters/__golden__/app-compose-services.json +24 -0
- package/src/adapters/__golden__/app-image-stages.json +24 -0
- package/src/adapters/__golden__/infra-main-sections.json +46 -0
- package/src/adapters/__golden__/orders-events-operations.json +24 -0
- package/src/adapters/__golden__/orders-proto-sections.json +57 -0
- package/src/adapters/__golden__/orders-schema-statements.json +68 -0
- package/src/adapters/__golden__/users-api-operations.json +46 -0
- package/src/adapters/__golden__/web-manifests-resources.json +24 -0
- package/src/adapters/asyncapi.ts +147 -0
- package/src/adapters/docker.ts +170 -0
- package/src/adapters/kubernetes.ts +103 -0
- package/src/adapters/markdown.ts +88 -0
- package/src/adapters/openapi.ts +156 -0
- package/src/adapters/protobuf.ts +286 -0
- package/src/adapters/shared.ts +151 -0
- package/src/adapters/sql.ts +555 -0
- package/src/adapters/terraform.ts +261 -0
- package/src/adapters/text.ts +72 -0
- package/src/config-validation.ts +41 -0
- package/src/coverage.ts +336 -0
- package/src/ids.ts +57 -0
- package/src/index.ts +105 -0
- package/src/registry.ts +155 -0
- package/src/staleness.ts +69 -0
- package/src/text/delimiter-balance.ts +295 -0
- package/src/text/line-index.ts +108 -0
- package/src/text/yaml-lite.ts +565 -0
package/NOTICE
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# NOTICE
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 Luke Bölling and contributors.
|
|
4
|
+
|
|
5
|
+
This attribution covers original Untangle work. Contributors retain copyright in
|
|
6
|
+
their contributions. Third-party packages, grammars and other materials retain
|
|
7
|
+
their own copyright holders, licenses and notices; this line does not replace them.
|
|
8
|
+
|
|
9
|
+
## License — binding decision
|
|
10
|
+
|
|
11
|
+
Untangle is licensed under the **GNU Affero General Public License, Version 3
|
|
12
|
+
(AGPL-3.0-only)**. See [`LICENSE`](./LICENSE).
|
|
13
|
+
|
|
14
|
+
This decision is **final, not provisional**, and was explicitly instructed by
|
|
15
|
+
the project owner. No contributor, agent or tool may change it without a new
|
|
16
|
+
explicit instruction.
|
|
17
|
+
|
|
18
|
+
The repository-wide license covers Untangle's first-party source code, scripts,
|
|
19
|
+
tests, fixtures, configuration, documentation, website and brand artwork, including
|
|
20
|
+
the logo and 3D yarn illustrations. Every workspace `package.json` declares
|
|
21
|
+
`"license": "AGPL-3.0-only"`. There is no separately licensed enterprise component
|
|
22
|
+
or separate permissive license for documentation or brand assets.
|
|
23
|
+
|
|
24
|
+
Third-party code, embedded grammars, dependency assets and their notices retain
|
|
25
|
+
their original licenses. Their inclusion does not relicense those works. The
|
|
26
|
+
repository license does not grant trademark rights or change the license of a
|
|
27
|
+
repository analyzed with Untangle. See the repository's `docs/LICENSING.md` for
|
|
28
|
+
scope and verification details.
|
|
29
|
+
|
|
30
|
+
This resolves the license question left open in `vision.md` §14 and completes
|
|
31
|
+
`product.md` §16, item 2.
|
|
32
|
+
|
|
33
|
+
## Rationale
|
|
34
|
+
|
|
35
|
+
- **Network copyleft fits the intended product model.** AGPL §13 sets source
|
|
36
|
+
availability requirements for modified versions used through a computer network.
|
|
37
|
+
The current local CLI/stdio-MCP entry and future shared-server modes share the
|
|
38
|
+
same license.
|
|
39
|
+
- **Local-first analysis has a clear boundary.** Running Untangle against a codebase
|
|
40
|
+
does not replace that codebase's own license with Untangle's license.
|
|
41
|
+
- **First-party components share one license.** Implemented components and future
|
|
42
|
+
official plugins use this repository's licensing policy. Planned integrations do
|
|
43
|
+
not establish implemented or published products.
|
|
44
|
+
|
|
45
|
+
## Deliberately accepted consequence
|
|
46
|
+
|
|
47
|
+
The project deliberately chooses AGPL rather than a permissive embedding license.
|
|
48
|
+
Redistribution, modifications and combined works must be assessed against the
|
|
49
|
+
actual terms in `LICENSE`, including applicable Corresponding Source requirements.
|
|
50
|
+
No permissive exception, commercial dual license or special contract-only package
|
|
51
|
+
license is granted or promised by this notice.
|
|
52
|
+
|
|
53
|
+
## Dependencies
|
|
54
|
+
|
|
55
|
+
Third-party runtime dependencies are checked against the allowlist in
|
|
56
|
+
`scripts/check-licenses.mjs`. Their licenses and copyright notices remain unchanged
|
|
57
|
+
and traceable through package `THIRD_PARTY_LICENSES.md` files, grammar provenance,
|
|
58
|
+
the lockfile and release SBOM. The allowlist includes MIT / Apache-2.0 / BSD / ISC
|
|
59
|
+
and other explicitly listed licenses; it is not a license grant for dependencies.
|
|
60
|
+
|
|
61
|
+
The allowlist in `scripts/check-licenses.mjs` applies to **dependencies**, not
|
|
62
|
+
workspace packages. Untangle's own packages carry AGPL-3.0-only and are excluded
|
|
63
|
+
from that check.
|
|
64
|
+
|
|
65
|
+
## Clean room
|
|
66
|
+
|
|
67
|
+
Untangle is an independent implementation with its own data models, APIs, tool
|
|
68
|
+
names, tests and sources. Feature ideas may be derived from publicly observable
|
|
69
|
+
behavior and generally known techniques. Third-party source code is not copied,
|
|
70
|
+
including from AGPL projects even where the license would permit it.
|
|
71
|
+
|
|
72
|
+
## Contribution terms
|
|
73
|
+
|
|
74
|
+
Pull requests are welcome under the repository's existing AGPL-3.0-only license.
|
|
75
|
+
No separate CLA, copyright assignment or DCO sign-off is required. Contributors
|
|
76
|
+
must have the right to submit their work under those terms. See CONTRIBUTING.md.
|
|
77
|
+
|
|
78
|
+
## Release decisions
|
|
79
|
+
|
|
80
|
+
- [x] Copyright attribution: Luke Bölling, with contributors retaining their rights.
|
|
81
|
+
- [x] Contributions use AGPL-3.0-only without an additional CLA or DCO gate.
|
|
82
|
+
|
|
83
|
+
## Open legal items before public beta
|
|
84
|
+
|
|
85
|
+
- [ ] Trademark search for the product name `Untangle`.
|
|
86
|
+
- [ ] Registration and legal review of the npm scope `@untangle-code`.
|
|
87
|
+
- [ ] Verify source availability and notices for each released network daemon/server
|
|
88
|
+
mode (WP-59). The current stdio MCP `untangle_about` already reports the license,
|
|
89
|
+
source repository and build commit when supplied; metadata alone does not prove
|
|
90
|
+
that a concrete deployed version's source is available.
|
package/README.md
CHANGED
|
@@ -1,3 +1,248 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @untangle-code/context-artifacts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Untangle context-artifact adapters: Markdown/ADR, SQL schema, OpenAPI, AsyncAPI, Protobuf,
|
|
4
|
+
Terraform/HCL, Kubernetes YAML, Docker/Compose, and a safe text fallback behind an
|
|
5
|
+
`ArtifactAdapter` registry (WP-15, spec.md §19, architecture.md §20). Internal, private package;
|
|
6
|
+
not published to npm.
|
|
7
|
+
|
|
8
|
+
Normative sources: [`spec.md`](../../spec.md) §19 (takes precedence in a conflict),
|
|
9
|
+
[`architecture.md`](../../architecture.md) §20, [`product.md`](../../product.md) §8.6.
|
|
10
|
+
|
|
11
|
+
Runtime dependencies: `@untangle-code/domain` (types/ports) and `@untangle-code/observability`
|
|
12
|
+
(typed errors). No network or filesystem access: files arrive already read through
|
|
13
|
+
`ArtifactInput.files`. spec.md §19 deliberately separates “how an artifact is read” from “how its
|
|
14
|
+
content is structured”; reading directories/files according to `ContextArtifactConfig.include`/
|
|
15
|
+
`exclude` is the indexer pipeline's responsibility, not this package's. No external YAML/JSON
|
|
16
|
+
library; see decision 1.
|
|
17
|
+
|
|
18
|
+
<a id="was-dieses-package-tut"></a>
|
|
19
|
+
|
|
20
|
+
## What this package does
|
|
21
|
+
|
|
22
|
+
`createArtifactAdapterRegistry()` (the sole main entry point alongside individual `create*Adapter()`
|
|
23
|
+
factories; see `src/index.ts`) returns an `ArtifactAdapter` (architecture.md §20:
|
|
24
|
+
`supports`/`read`/`chunk`/`links`) that dispatches to the appropriate format implementation:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
createOpenApiAdapter() — openapi.ts — one section per operation, preserves operationId
|
|
28
|
+
createAsyncApiAdapter() — asyncapi.ts — one section per channel operation
|
|
29
|
+
createDockerAdapter() — docker.ts — Dockerfile stages, Compose services
|
|
30
|
+
createKubernetesAdapter() — kubernetes.ts — one resource per YAML document
|
|
31
|
+
createMarkdownAdapter() — markdown.ts — sections by heading, with breadcrumb
|
|
32
|
+
createSqlAdapter() — sql.ts — tables/views/indexes/constraints
|
|
33
|
+
createProtobufAdapter() — protobuf.ts — messages/services/RPCs
|
|
34
|
+
createTerraformAdapter() — terraform.ts — resources/modules/variables/outputs
|
|
35
|
+
createTextFallbackAdapter()— text.ts — fixed line-window fallback (catch-all)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Every adapter chunks by **semantic sections**, not fixed line windows (spec.md §19.2 SHOULD), except
|
|
39
|
+
the text fallback, whose purpose is precisely fixed line windows. Each chunk carries a byte-exact
|
|
40
|
+
`SourceRange` (`text/line-index.ts`) and exact raw content, never a summary or reformatting
|
|
41
|
+
(spec.md §19.2).
|
|
42
|
+
|
|
43
|
+
This package also exports (see “Decisions” below for the reasoning):
|
|
44
|
+
|
|
45
|
+
- `validateArtifactConfigs()`: project-wide name uniqueness (spec.md §19.2).
|
|
46
|
+
- `computeArtifactStalenessSnapshot()` / `isArtifactStale()`: configuration hash + content hash
|
|
47
|
+
(spec.md §19.2).
|
|
48
|
+
- `buildArtifactSkipLedger()`: a Skip Ledger with reasons for byte ranges not included in a chunk
|
|
49
|
+
AND for fragments cut through a construct (BUILD_PLAN §12 E4, see decision 10).
|
|
50
|
+
- `findDelimiterImbalances()` / `describeDelimiterImbalances()` (`text/delimiter-balance.ts`): the
|
|
51
|
+
plausibility check itself, reusable for custom additional adapters.
|
|
52
|
+
- `parseYamlNode()`/`splitYamlDocuments()` (`text/yaml-lite.ts`): the internally used YAML-subset
|
|
53
|
+
parser, reusable for custom additional adapters.
|
|
54
|
+
|
|
55
|
+
<a id="modulübersicht"></a>
|
|
56
|
+
|
|
57
|
+
## Module overview
|
|
58
|
+
|
|
59
|
+
| Module | Contents |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `registry.ts` | `ArtifactAdapterRegistry`, `createArtifactAdapterRegistry()`: dispatch + text fallback |
|
|
62
|
+
| `adapters/*.ts` | one `ArtifactAdapter` per format, each with its own golden test |
|
|
63
|
+
| `adapters/shared.ts` | `buildDocuments()`, `buildChunks()`, `wholeDocumentDraft()`: shared building blocks |
|
|
64
|
+
| `text/line-index.ts` | `LineIndex`, `rangeForLines()`, `sliceLines()`: byte-exact `SourceRange`s from line spans |
|
|
65
|
+
| `text/yaml-lite.ts` | minimal YAML-subset parser (block mappings/sequences, single-line flow, `\|`/`>`, `---`) |
|
|
66
|
+
| `ids.ts` | `buildArtifactChunkId()`: deterministic `chunkId` derivation |
|
|
67
|
+
| `config-validation.ts` | `validateArtifactConfigs()` |
|
|
68
|
+
| `staleness.ts` | `computeArtifactStalenessSnapshot()`, `isArtifactStale()` |
|
|
69
|
+
| `text/delimiter-balance.ts` | `findDelimiterImbalances()`: plausibility checks for cut fragments (WP-118) |
|
|
70
|
+
| `coverage.ts` | `buildArtifactSkipLedger()`, `ChunkPlausibilityCheck` |
|
|
71
|
+
|
|
72
|
+
`src/index.ts` is the sole public export (rule 10).
|
|
73
|
+
|
|
74
|
+
<a id="entscheidungen"></a>
|
|
75
|
+
|
|
76
|
+
## Decisions
|
|
77
|
+
|
|
78
|
+
Where spec.md/architecture.md leave a question open, or where the `ArtifactAdapter` contract in
|
|
79
|
+
`@untangle-code/domain` (another package, not editable) is narrower than this assignment, the
|
|
80
|
+
following decisions apply:
|
|
81
|
+
|
|
82
|
+
1. **No external YAML/JSON library: `text/yaml-lite.ts` is a locally written subset parser.** This
|
|
83
|
+
package may write only within `packages/context-artifacts/**`; any new npm dependency would
|
|
84
|
+
require editing root `pnpm-lock.yaml`, potentially modified simultaneously by six agents in the
|
|
85
|
+
same wave. The local parser covers precisely the constructs OpenAPI/AsyncAPI/Kubernetes/Compose
|
|
86
|
+
documents use in practice: indentation-based block mappings/sequences, single-line flow
|
|
87
|
+
collections (`{a: 1}`, `[a, b]`), scalars (plain/'/"), `\|`/`>` block scalars, `#` comments,
|
|
88
|
+
`---`/`...` document separators. NOT supported: anchors/aliases, tags, multiline flow, complex
|
|
89
|
+
non-scalar mapping keys. Documents requiring these degrade visibly (`YamlParseError` →
|
|
90
|
+
`"*_parse_error"` chunk with full raw content, never a crash). This limit is deliberate; see
|
|
91
|
+
`openIssues` for the recommendation to replace it with a full library if needed, once a package
|
|
92
|
+
outside the current parallel wave may edit `pnpm-lock.yaml`.
|
|
93
|
+
|
|
94
|
+
2. **`ContextArtifactType` (spec.md §19.1, `@untangle-code/domain`, not editable) lacks
|
|
95
|
+
`"docker"`/`"compose"` literals, although architecture.md §20 explicitly requires a
|
|
96
|
+
Docker/Compose adapter.** Documents from `docker.ts` therefore carry `type: "text"` (the
|
|
97
|
+
closest fit in the frozen contract); `language: "dockerfile"` or `"docker-compose"` (an existing
|
|
98
|
+
general optional field) carries the actual subtype. `supports()` recognizes Dockerfile/Compose
|
|
99
|
+
filenames for both `type === "auto"` and `type === "text"`. The latter matters because `"text"`
|
|
100
|
+
is the only literal available for explicitly configuring such an artifact. `type: "text"` on
|
|
101
|
+
a file named `Dockerfile` must still receive actual stage chunking rather than mere line-window
|
|
102
|
+
degradation, while generic `type: "text"` on `notes.txt` still reaches the text fallback
|
|
103
|
+
(neither filename regex matches). See `openIssues`: spec.md §19.1 should add
|
|
104
|
+
`"docker"`/`"compose"`.
|
|
105
|
+
|
|
106
|
+
3. **Registry `chunk()`/`links()` dispatch reuses `adapter.supports(document.type, document.path)`
|
|
107
|
+
rather than introducing another identity mechanism.** Every `ArtifactDocument.type` other than
|
|
108
|
+
`"text"` already is the exact literal checked by its adapter (`"sql"`, `"openapi"`, …), so reuse
|
|
109
|
+
works without extra machinery. An adapter identity field on `ArtifactDocument` would require
|
|
110
|
+
extending the other package's frozen `domain` contract. A custom additional adapter
|
|
111
|
+
(`registry.register(...)`) that also produces `type: "text"` therefore MUST handle `"text"`
|
|
112
|
+
alongside `"auto"` in its own `supports()`, so it is rediscovered after reading. Documented in
|
|
113
|
+
`registry.ts` and covered by a dedicated test (`registry.test.ts`).
|
|
114
|
+
|
|
115
|
+
4. **The Skip Ledger (`buildArtifactSkipLedger()`) is a derived utility in this package, not a
|
|
116
|
+
field on `ArtifactChunk`/`ArtifactAdapter`.** BUILD_PLAN §12 E4 requires skipped content never to
|
|
117
|
+
disappear silently, but the `ArtifactAdapter` contract (architecture.md §20, `domain`, not
|
|
118
|
+
editable) has no ledger field. `chunk()` therefore keeps the exact contract (`ArtifactChunk[]`).
|
|
119
|
+
`buildArtifactSkipLedger(document, chunks)` subsequently derives every uncovered byte range
|
|
120
|
+
from returned chunk ranges and classifies it (`"whitespace_or_comment"` vs.
|
|
121
|
+
`"unclassified_content"`), allowing callers (indexer/CLI) to log it. The text fallback always
|
|
122
|
+
has an empty ledger (full coverage by construction); for Terraform, for example, gaps consisting
|
|
123
|
+
of comments/blank lines between blocks are expected and marked accordingly, not as errors.
|
|
124
|
+
|
|
125
|
+
5. **`ArtifactChunk.chunkId` has no spec.md §5 derivation formula** (§5 defines `chunk_id` only for
|
|
126
|
+
code chunks). `ids.ts::buildArtifactChunkId()` mirrors `domain.buildChunkId` (UUIDv5 under the
|
|
127
|
+
Untangle namespace, kind-tagged components separated by U+001F: artifact name, document path,
|
|
128
|
+
semantic kind string, ordinal). Deterministic for identical inputs, collision-free across
|
|
129
|
+
artifacts/documents/kinds.
|
|
130
|
+
|
|
131
|
+
6. **Markdown sections form a FLAT partition, not a nested one.** A heading section ends at the
|
|
132
|
+
next heading of ANY level, not only one `<=` its own level. Otherwise an H1 section would consume
|
|
133
|
+
all content until the next H1 and overlap its own subsections. Hierarchy remains fully visible:
|
|
134
|
+
`title` carries the breadcrumb (`"H1 › H2 › H3"`), while `content` remains exact raw section text.
|
|
135
|
+
|
|
136
|
+
7. **Protobuf `service`/`rpc` chunks overlap DELIBERATELY** (a `protobuf_rpc` chunk lies wholly
|
|
137
|
+
inside its `protobuf_service` chunk's range), the package's sole deliberate exception to “flat,
|
|
138
|
+
non-overlapping partition”. Searching for an exact RPC name should find the narrow RPC chunk;
|
|
139
|
+
searching for service-wide options/comments should still find the full service context.
|
|
140
|
+
`buildArtifactSkipLedger()` merges overlapping ranges before finding gaps, avoiding false gaps
|
|
141
|
+
(see `coverage.test.ts`).
|
|
142
|
+
|
|
143
|
+
8. **SQL statement boundaries are byte-exact, not line-based.** Two statements on one physical
|
|
144
|
+
line (`CREATE TABLE a (id int); CREATE TABLE b (id int);`) receive separate, non-overlapping
|
|
145
|
+
byte ranges. Only when nothing substantive follows `;` on the same line does the closed
|
|
146
|
+
statement take the rest of that line (whitespace, an appended `--` comment, newline).
|
|
147
|
+
|
|
148
|
+
9. **SQL dialect is selected, not guessed; default `"backslash-escapes"` (WP-118).** Dialects
|
|
149
|
+
conflict: in standard SQL, `\` in a literal is ordinary; common extensions let `\'` escape a
|
|
150
|
+
quote (alongside `` ` `` identifiers and `#` line comments). The dialect cannot be read from
|
|
151
|
+
the file, so the caller selects it: `createSqlAdapter({ dialect })` with
|
|
152
|
+
`SqlDialect = "backslash-escapes" | "doubled-quotes-only"`, default `DEFAULT_SQL_DIALECT`.
|
|
153
|
+
|
|
154
|
+
Default rationale (asymmetric error costs for a SPLITTER): the expensive error is a cut THROUGH
|
|
155
|
+
a statement, producing two syntactically broken chunks. The cheaper error is cutting too late:
|
|
156
|
+
two statements in one chunk that still contains valid SQL. Reading `\'` as an escape can only
|
|
157
|
+
affect a literal ending in `\` directly before its closing delimiter (rare, and the plausibility
|
|
158
|
+
check reports it). NOT reading `\'` as an escape tears apart every literal containing `\'`
|
|
159
|
+
followed by `;`: the reported defect, common in dumps. Across dialects, the splitter also
|
|
160
|
+
recognizes `$tag$` literals, where `;` is equally common.
|
|
161
|
+
|
|
162
|
+
10. **Cut-boundary plausibility checks (`text/delimiter-balance.ts`, WP-118): E4 also applies when
|
|
163
|
+
nothing is MISSING but something is CUT INCORRECTLY.** `buildArtifactSkipLedger()` calculated
|
|
164
|
+
byte coverage and was therefore blind to a fragment cut through a string, comment, heredoc,
|
|
165
|
+
or bracketed construct: coverage complete, no empty/overlapping chunks, ledger zero. The
|
|
166
|
+
ledger now accepts a format-specific check
|
|
167
|
+
(`buildArtifactSkipLedger(document, chunks, { checkPlausibility })`) and reports each flagged
|
|
168
|
+
fragment with reason `"implausible_fragment"`. Adapters supply their check through
|
|
169
|
+
`buildChunksReportingSkips`, so callers do not have to remember it.
|
|
170
|
+
|
|
171
|
+
The checker may be **more suspicious** than the splitter: a false alarm costs one warning
|
|
172
|
+
line; an undisclosed bad cut costs an unusable chunk. SQL checks therefore recognize backtick
|
|
173
|
+
identifiers in both dialects, making an incorrect dialect selection visible rather than silent.
|
|
174
|
+
|
|
175
|
+
Review of the remaining adapters for the same blind spot:
|
|
176
|
+
|
|
177
|
+
| Adapter | Finding | Action |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `sql.ts` | trigger: `\'` and `$tag$` tore statements apart | dialect + check |
|
|
180
|
+
| `terraform.ts` | `{` inside a heredoc permanently shifted the bracket counter; an unclosed block “ran to end of file” and consumed later blocks, both without ledger entries | account for heredocs in counter; check |
|
|
181
|
+
| `protobuf.ts` | `rpc … {` ended at the first `;` in its options body; an unclosed block “ran to end of file” | bracket depth in `findRpcEnd`; check |
|
|
182
|
+
| `markdown.ts` | unaffected: `findHeadings` recognizes code fences; a heading within a fence does not split | none |
|
|
183
|
+
| `docker.ts` | unaffected: `HEREDOC_OPEN_RE` already exists; `FROM` is recognized only on complete lines | none |
|
|
184
|
+
| `openapi.ts`, `asyncapi.ts`, `kubernetes.ts` | unaffected: they delegate splitting to `text/yaml-lite.ts`, which degrades visibly with `YamlParseError` on unreadable input | none |
|
|
185
|
+
| `text.ts` | unaffected: fixed line windows, full coverage by construction | none |
|
|
186
|
+
|
|
187
|
+
<a id="bekannte-grenzen--offene-punkte"></a>
|
|
188
|
+
|
|
189
|
+
## Known limits / open issues
|
|
190
|
+
|
|
191
|
+
- The plausibility check is a **delimiter checker, not a parser**. It detects “cut through a
|
|
192
|
+
construct”, not “semantically incomplete”: a fragment such as `FROM orders WHERE` is balanced
|
|
193
|
+
and passes. A bad cut also goes undetected if both halves happen to be balanced, e.g. a `#`
|
|
194
|
+
comment containing `;` read under `dialect: "doubled-quotes-only"`.
|
|
195
|
+
- The SQL splitter recognizes `$tag$` literals but not dialect-specific forms such as `DELIMITER`
|
|
196
|
+
switches in scripts.
|
|
197
|
+
- `terraform.ts` still recognizes two block openings on ONE physical line only once (see module
|
|
198
|
+
comment). Nothing is lost; the second block simply receives neither its own chunk nor title.
|
|
199
|
+
|
|
200
|
+
For further points, see `openIssues` in this work unit's final report.
|
|
201
|
+
|
|
202
|
+
<a id="e4-ehrlichkeitspruefung-wp-165"></a>
|
|
203
|
+
|
|
204
|
+
## E4 honesty checks (WP-165)
|
|
205
|
+
|
|
206
|
+
`src/honesty.test.ts` runs `assertHonestUnderDamage` from `@untangle-code/testkit` against the full
|
|
207
|
+
`read()` → `chunk()` path through registry dispatch order, for four formats (Markdown, SQL,
|
|
208
|
+
Kubernetes YAML, OpenAPI). Because `read()` accepts `PortCallOptions`, cancellation damage really
|
|
209
|
+
applies here, unlike the chunker and language adapters. All six damage types run; none is exempted.
|
|
210
|
+
|
|
211
|
+
Two things are checked: (1) how much of the document appears NEITHER in a chunk NOR in a ledger
|
|
212
|
+
entry actually reported through `onSkipLedgerGap` (zero when healthy); (2) whether chunks themselves
|
|
213
|
+
are truthful (`content` byte-exact for their own `range`, matching `contentHash`, no empty/inverted
|
|
214
|
+
range).
|
|
215
|
+
|
|
216
|
+
**Result: all four formats pass.** Cancellation is reported loudly as `JOB_CANCELLED`, not swallowed.
|
|
217
|
+
|
|
218
|
+
**Lesson (also documented in testkit):** projection must target UNREPORTED loss, not loss itself.
|
|
219
|
+
A gap properly written to the ledger satisfies E4; its size legitimately shrinks with a shorter
|
|
220
|
+
document. Comparing that size creates a false alarm for every size damage.
|
|
221
|
+
|
|
222
|
+
<a id="bekannte-grenzen-e4"></a>
|
|
223
|
+
|
|
224
|
+
### Known limits (E4)
|
|
225
|
+
|
|
226
|
+
- **The OpenAPI adapter leaves the version line and structural keys unclaimed.** In a healthy
|
|
227
|
+
OpenAPI document, `openapi: 3.0.3`, the `info:` line itself, and path/method lines (`paths:`,
|
|
228
|
+
`/invoices:`, `get:`) stay outside every chunk and appear as `unclassified_content` in the Skip
|
|
229
|
+
Ledger. E4-compliant (reported, not silent), but unsatisfactory in content: the API version is
|
|
230
|
+
never searchable. Not fixed in WP-165, because this shifts chunk boundaries and golden files
|
|
231
|
+
for several adapters; that requires a separate assignment with golden review.
|
|
232
|
+
|
|
233
|
+
<a id="lizenz"></a>
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
**AGPL-3.0-only.** This package includes the complete license in [`LICENSE`](./LICENSE), the
|
|
239
|
+
license-choice rationale in [`NOTICE`](./NOTICE), and licenses for all third-party production
|
|
240
|
+
dependencies in [`THIRD_PARTY_LICENSES.md`](./THIRD_PARTY_LICENSES.md). All three ship in the npm
|
|
241
|
+
tarball: AGPL-3.0-only §4 and §5 require recipients to receive the license; a README alone is not
|
|
242
|
+
a copy of the license.
|
|
243
|
+
|
|
244
|
+
Local use alone does not trigger an obligation. Copyleft obligations apply to redistribution and
|
|
245
|
+
providing a network service (AGPL §13).
|
|
246
|
+
|
|
247
|
+
Corresponding TypeScript source ships under `src/` in the same tarball; the complete repository
|
|
248
|
+
is available at <https://github.com/lboel/untangle>.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Third-party licenses — @untangle-code/context-artifacts
|
|
2
|
+
|
|
3
|
+
Generated by `pnpm third-party-licenses` from this package's installed
|
|
4
|
+
production dependency tree: transitive `dependencies`, across
|
|
5
|
+
workspace edges, with no `devDependencies` included. The generator reads
|
|
6
|
+
each resolved package's `package.json` in `node_modules`; nothing is maintained by hand
|
|
7
|
+
or estimated.
|
|
8
|
+
|
|
9
|
+
Do not edit manually: `pnpm third-party-licenses --check` fails on drift,
|
|
10
|
+
and `pnpm check-publishable` rejects packages without a current file.
|
|
11
|
+
|
|
12
|
+
This file exists because `docs/BUILD_PLAN.md`, risk R6, makes
|
|
13
|
+
per-package `THIRD_PARTY_LICENSES.md` a release gate for npm publication.
|
|
14
|
+
|
|
15
|
+
Untangle itself is **AGPL-3.0-only** (see `LICENSE` and `NOTICE` in
|
|
16
|
+
this package). The packages below are third-party works; their own
|
|
17
|
+
licenses and copyright notices continue to apply unchanged. Workspace
|
|
18
|
+
packages (`@untangle-code/*`) are not listed here; they belong to the same
|
|
19
|
+
work and use the same AGPL-3.0-only license.
|
|
20
|
+
|
|
21
|
+
## Result: no third-party dependencies
|
|
22
|
+
|
|
23
|
+
This package has no production dependencies outside the workspace.
|
|
24
|
+
The list is empty because none exist, not because they were not checked.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncAPI adapter (architecture.md §20: "je Channel ein Abschnitt,
|
|
3
|
+
* Operation-IDs erhalten").
|
|
4
|
+
*
|
|
5
|
+
* AsyncAPI v2's shape (`channels.<channel>.subscribe`/`.publish`, each
|
|
6
|
+
* optionally carrying `operationId`) is what this adapter targets — one
|
|
7
|
+
* chunk per channel operation, kind `"asyncapi_operation"`, titled
|
|
8
|
+
* `"<channel> <subscribe|publish> (<operationId>)"`. A channel with
|
|
9
|
+
* neither `subscribe` nor `publish` (rare, but not invalid AsyncAPI) still
|
|
10
|
+
* gets a whole-channel `"asyncapi_channel"` chunk rather than being
|
|
11
|
+
* dropped. Malformed documents degrade the same way `openapi.ts` does.
|
|
12
|
+
*/
|
|
13
|
+
import type { ArtifactAdapter } from "@untangle-code/domain";
|
|
14
|
+
import type { SkipLedgerReporter } from "./shared.js";
|
|
15
|
+
export interface AsyncApiAdapterOptions {
|
|
16
|
+
/** Overrides how a real skip-ledger finding surfaces from `chunk()` (default: a stderr warning) — see `coverage.ts`'s `SkipLedgerReporter`. */
|
|
17
|
+
onSkipLedgerGap?: SkipLedgerReporter;
|
|
18
|
+
}
|
|
19
|
+
export declare function createAsyncApiAdapter(options?: AsyncApiAdapterOptions): ArtifactAdapter;
|
|
20
|
+
//# sourceMappingURL=asyncapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncapi.d.ts","sourceRoot":"","sources":["../../src/adapters/asyncapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAW/B,OAAO,KAAK,EAAc,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAuElE,MAAM,WAAW,sBAAsB;IACrC,+IAA+I;IAC/I,eAAe,CAAC,EAAE,kBAAkB,CAAC;CACtC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAyC3F"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { buildLineIndex } from "../text/line-index.js";
|
|
2
|
+
import { mapGet, parseYamlNode, scalarString, YamlParseError, } from "../text/yaml-lite.js";
|
|
3
|
+
import { buildChunks, buildChunksReportingSkips, buildDocuments, wholeDocumentDraft, } from "./shared.js";
|
|
4
|
+
const ASYNCAPI_NAME_HINT = /asyncapi/i;
|
|
5
|
+
const YAML_OR_JSON_EXTENSION = /\.(ya?ml|json)$/i;
|
|
6
|
+
const OPERATION_KEYS = ["subscribe", "publish"];
|
|
7
|
+
function asyncApiDrafts(root) {
|
|
8
|
+
if (root === null || root.kind !== "map")
|
|
9
|
+
return [];
|
|
10
|
+
const drafts = [];
|
|
11
|
+
const channels = mapGet(root, "channels");
|
|
12
|
+
if (channels === undefined || channels.kind !== "map")
|
|
13
|
+
return drafts;
|
|
14
|
+
for (const channelEntry of channels.entries) {
|
|
15
|
+
if (channelEntry.value.kind !== "map")
|
|
16
|
+
continue;
|
|
17
|
+
let foundOperation = false;
|
|
18
|
+
for (const operationKey of OPERATION_KEYS) {
|
|
19
|
+
const operation = mapGet(channelEntry.value, operationKey);
|
|
20
|
+
if (operation === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
foundOperation = true;
|
|
23
|
+
const operationId = scalarString(mapGet(operation, "operationId"));
|
|
24
|
+
const title = operationId === undefined
|
|
25
|
+
? `${channelEntry.key} ${operationKey}`
|
|
26
|
+
: `${channelEntry.key} ${operationKey} (${operationId})`;
|
|
27
|
+
drafts.push({
|
|
28
|
+
kind: "asyncapi_operation",
|
|
29
|
+
startLine: operation.startLine,
|
|
30
|
+
endLine: operation.endLine,
|
|
31
|
+
title,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (!foundOperation) {
|
|
35
|
+
drafts.push({
|
|
36
|
+
kind: "asyncapi_channel",
|
|
37
|
+
startLine: channelEntry.value.startLine,
|
|
38
|
+
endLine: channelEntry.value.endLine,
|
|
39
|
+
title: channelEntry.key,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return drafts;
|
|
44
|
+
}
|
|
45
|
+
function operationIdOf(chunk) {
|
|
46
|
+
if (chunk.kind !== "asyncapi_operation" || chunk.title === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const match = /\(([^()]+)\)$/.exec(chunk.title);
|
|
49
|
+
return match?.[1];
|
|
50
|
+
}
|
|
51
|
+
function draftsFor(document) {
|
|
52
|
+
try {
|
|
53
|
+
return asyncApiDrafts(parseYamlNode(document.content));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const reason = error instanceof YamlParseError ? error.message : "unknown parse error";
|
|
57
|
+
return [
|
|
58
|
+
wholeDocumentDraft("asyncapi_parse_error", buildLineIndex(document.content), `parse error: ${reason}`),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function createAsyncApiAdapter(options = {}) {
|
|
63
|
+
return {
|
|
64
|
+
supports(type, path) {
|
|
65
|
+
return (type === "asyncapi" ||
|
|
66
|
+
(type === "auto" && ASYNCAPI_NAME_HINT.test(path) && YAML_OR_JSON_EXTENSION.test(path)));
|
|
67
|
+
},
|
|
68
|
+
async read(input) {
|
|
69
|
+
return buildDocuments(input, "asyncapi");
|
|
70
|
+
},
|
|
71
|
+
chunk(document) {
|
|
72
|
+
return buildChunksReportingSkips(document, buildLineIndex(document.content), draftsFor(document), options.onSkipLedgerGap);
|
|
73
|
+
},
|
|
74
|
+
/** Heuristic, same convention as the OpenAPI adapter's `operationId` link. */
|
|
75
|
+
links(document) {
|
|
76
|
+
const chunks = buildChunks(document, buildLineIndex(document.content), draftsFor(document));
|
|
77
|
+
const links = [];
|
|
78
|
+
for (const chunk of chunks) {
|
|
79
|
+
const operationId = operationIdOf(chunk);
|
|
80
|
+
if (operationId === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
links.push({
|
|
83
|
+
fromChunkId: chunk.chunkId,
|
|
84
|
+
targetKind: "symbol",
|
|
85
|
+
targetName: operationId,
|
|
86
|
+
heuristic: true,
|
|
87
|
+
confidence: 0.5,
|
|
88
|
+
note: `naming-convention guess: AsyncAPI operationId "${operationId}" → a handler method of the same name`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return links;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=asyncapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncapi.js","sourceRoot":"","sources":["../../src/adapters/asyncapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker / Compose adapter (architecture.md §20: "Services, Stages").
|
|
3
|
+
*
|
|
4
|
+
* spec.md §19.1's `ContextArtifactType` union has no `"docker"` (or
|
|
5
|
+
* `"compose"`) literal — see README "Entscheidungen" and this WP's
|
|
6
|
+
* openIssues entry. `ArtifactDocument.type` is therefore `"text"` for
|
|
7
|
+
* documents this adapter produces (the closest fit in the frozen contract).
|
|
8
|
+
* `supports()` matches on filename for BOTH `type === "auto"` and
|
|
9
|
+
* `type === "text"` — `"text"` is the only literal an explicit config
|
|
10
|
+
* could even use for a Dockerfile/Compose artifact, so treating it the
|
|
11
|
+
* same as `"auto"` here is what lets an explicit `type: "text"` still get
|
|
12
|
+
* real stage/service chunking instead of being indistinguishable from a
|
|
13
|
+
* plain text file; the registry's `resolve()` tries this adapter before
|
|
14
|
+
* the text fallback either way, so a genuine plain-text file is never
|
|
15
|
+
* misrouted here. `ArtifactDocument.language` carries which of the two a
|
|
16
|
+
* given document is (`"dockerfile"` / `"docker-compose"`), so this
|
|
17
|
+
* adapter's own `chunk()` (and the registry's document→adapter dispatch)
|
|
18
|
+
* can tell a Dockerfile from a Compose file from a genuinely generic text
|
|
19
|
+
* document — `language` is a general-purpose optional field on the frozen
|
|
20
|
+
* contract, so this uses it without needing any contract change.
|
|
21
|
+
*
|
|
22
|
+
* Dockerfile: each `FROM ... [AS name]` starts a build stage that runs to
|
|
23
|
+
* the line before the next `FROM` (or EOF) — kind `"docker_stage"`.
|
|
24
|
+
* Compose: each entry under top-level `services:` becomes one chunk — kind
|
|
25
|
+
* `"compose_service"`.
|
|
26
|
+
*
|
|
27
|
+
* `FROM` detection skips heredoc bodies (WP-81 review's defect class,
|
|
28
|
+
* applied here: a separator-looking token inside a "quoted" region must
|
|
29
|
+
* never be mistaken for a real one). A modern Dockerfile can write
|
|
30
|
+
* `RUN <<EOF` / `COPY <<-EOF /dst` and have arbitrary script text up to a
|
|
31
|
+
* closing `EOF` line — that text is user content, not Dockerfile
|
|
32
|
+
* instructions, so a line reading e.g. `FROM scratch` inside such a heredoc
|
|
33
|
+
* (a Python/shell snippet building its OWN Dockerfile-ish text, or just a
|
|
34
|
+
* coincidence) must never be read as a new build stage.
|
|
35
|
+
*/
|
|
36
|
+
import type { ArtifactAdapter } from "@untangle-code/domain";
|
|
37
|
+
import type { SkipLedgerReporter } from "./shared.js";
|
|
38
|
+
export interface DockerAdapterOptions {
|
|
39
|
+
/** Overrides how a real skip-ledger finding surfaces from `chunk()` (default: a stderr warning) — see `coverage.ts`'s `SkipLedgerReporter`. */
|
|
40
|
+
onSkipLedgerGap?: SkipLedgerReporter;
|
|
41
|
+
}
|
|
42
|
+
export declare function createDockerAdapter(options?: DockerAdapterOptions): ArtifactAdapter;
|
|
43
|
+
//# sourceMappingURL=docker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/adapters/docker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAc,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA0ElE,MAAM,WAAW,oBAAoB;IACnC,+IAA+I;IAC/I,eAAe,CAAC,EAAE,kBAAkB,CAAC;CACtC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,eAAe,CA4CvF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { buildLineIndex, splitLines } from "../text/line-index.js";
|
|
2
|
+
import { mapGet, parseYamlNode, YamlParseError } from "../text/yaml-lite.js";
|
|
3
|
+
import { buildChunksReportingSkips, buildDocuments, wholeDocumentDraft } from "./shared.js";
|
|
4
|
+
const DOCKERFILE_NAME = /(^|\/)Dockerfile(\.[\w-]+)?$/;
|
|
5
|
+
const COMPOSE_NAME = /(^|\/)(docker-)?compose\.ya?ml$/i;
|
|
6
|
+
const FROM_RE = /^\s*FROM\s+(\S+)(?:\s+AS\s+(\S+))?\s*$/i;
|
|
7
|
+
/** `RUN <<EOF`, `COPY <<-'EOF' /dst`, … — an optional `-` (strip leading tabs on the closing line) and an optional quoted/bare delimiter word. */
|
|
8
|
+
const HEREDOC_OPEN_RE = /<<(-)?\s*(['"]?)([A-Za-z_][\w]*)\2/;
|
|
9
|
+
function dockerfileDrafts(lines) {
|
|
10
|
+
const stageStarts = [];
|
|
11
|
+
let heredocDelimiter = null;
|
|
12
|
+
let heredocStripLeadingTabs = false;
|
|
13
|
+
for (const [index, line] of lines.entries()) {
|
|
14
|
+
if (heredocDelimiter !== null) {
|
|
15
|
+
const closingCandidate = heredocStripLeadingTabs ? line.replace(/^\t+/, "") : line;
|
|
16
|
+
if (closingCandidate === heredocDelimiter) {
|
|
17
|
+
heredocDelimiter = null;
|
|
18
|
+
}
|
|
19
|
+
continue; // heredoc body: never Dockerfile instructions, whatever they contain
|
|
20
|
+
}
|
|
21
|
+
const heredocOpen = HEREDOC_OPEN_RE.exec(line);
|
|
22
|
+
if (heredocOpen) {
|
|
23
|
+
heredocDelimiter = heredocOpen[3];
|
|
24
|
+
heredocStripLeadingTabs = heredocOpen[1] === "-";
|
|
25
|
+
continue; // the opener line itself (e.g. "RUN <<EOF") is never a FROM either
|
|
26
|
+
}
|
|
27
|
+
const match = FROM_RE.exec(line);
|
|
28
|
+
if (match) {
|
|
29
|
+
const entry = {
|
|
30
|
+
line: index + 1,
|
|
31
|
+
base: match[1],
|
|
32
|
+
};
|
|
33
|
+
stageStarts.push(match[2] === undefined ? entry : { ...entry, name: match[2] });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const totalLines = Math.max(lines.length, 1);
|
|
37
|
+
return stageStarts.map((stage, position) => {
|
|
38
|
+
const endLine = position + 1 < stageStarts.length ? stageStarts[position + 1].line - 1 : totalLines;
|
|
39
|
+
return {
|
|
40
|
+
kind: "docker_stage",
|
|
41
|
+
startLine: stage.line,
|
|
42
|
+
endLine,
|
|
43
|
+
title: stage.name === undefined ? `stage ${position} (${stage.base})` : stage.name,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function composeDrafts(document) {
|
|
48
|
+
try {
|
|
49
|
+
const root = parseYamlNode(document.content);
|
|
50
|
+
const services = mapGet(root, "services");
|
|
51
|
+
if (services === undefined || services.kind !== "map")
|
|
52
|
+
return [];
|
|
53
|
+
return services.entries.map((entry) => ({
|
|
54
|
+
kind: "compose_service",
|
|
55
|
+
startLine: entry.value.startLine,
|
|
56
|
+
endLine: entry.value.endLine,
|
|
57
|
+
title: entry.key,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const reason = error instanceof YamlParseError ? error.message : "unknown parse error";
|
|
62
|
+
return [
|
|
63
|
+
wholeDocumentDraft("compose_parse_error", buildLineIndex(document.content), `parse error: ${reason}`),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function createDockerAdapter(options = {}) {
|
|
68
|
+
return {
|
|
69
|
+
supports(type, path) {
|
|
70
|
+
return ((type === "auto" || type === "text") &&
|
|
71
|
+
(DOCKERFILE_NAME.test(path) || COMPOSE_NAME.test(path)));
|
|
72
|
+
},
|
|
73
|
+
async read(input) {
|
|
74
|
+
const dockerfiles = input.files.filter((file) => DOCKERFILE_NAME.test(file.path));
|
|
75
|
+
const composeFiles = input.files.filter((file) => COMPOSE_NAME.test(file.path));
|
|
76
|
+
return [
|
|
77
|
+
...buildDocuments({ config: input.config, files: dockerfiles }, "text", "dockerfile"),
|
|
78
|
+
...buildDocuments({ config: input.config, files: composeFiles }, "text", "docker-compose"),
|
|
79
|
+
];
|
|
80
|
+
},
|
|
81
|
+
chunk(document) {
|
|
82
|
+
const index = buildLineIndex(document.content);
|
|
83
|
+
if (document.language === "dockerfile") {
|
|
84
|
+
const { lines } = splitLines(document.content);
|
|
85
|
+
return buildChunksReportingSkips(document, index, dockerfileDrafts(lines), options.onSkipLedgerGap);
|
|
86
|
+
}
|
|
87
|
+
// Anything else (including "docker-compose" and any unrecognized
|
|
88
|
+
// label an external caller might set) is treated as Compose YAML —
|
|
89
|
+
// the only other shape this adapter's own `read()` ever produces.
|
|
90
|
+
return buildChunksReportingSkips(document, index, composeDrafts(document), options.onSkipLedgerGap);
|
|
91
|
+
},
|
|
92
|
+
links() {
|
|
93
|
+
return [];
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/adapters/docker.ts"],"names":[],"mappings":"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"}
|