@sdd-method/sdd-cli 0.110.2 → 0.111.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/dist/lib/sync/adr-transitions.d.ts +43 -0
- package/dist/lib/sync/adr-transitions.d.ts.map +1 -0
- package/dist/lib/sync/adr-transitions.js +177 -0
- package/dist/lib/sync/adr-transitions.js.map +1 -0
- package/dist/lib/sync/index.d.ts.map +1 -1
- package/dist/lib/sync/index.js +19 -1
- package/dist/lib/sync/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/README.md +37 -11
- package/scripts/sync-vendored-script.sh +65 -25
- package/scripts/validate-app-dependencies.sh +43 -8
- package/scripts/validate-integration-docs.sh +182 -0
- package/scripts/sync-method-baseline.sh +0 -660
|
@@ -1,660 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# sync-method-baseline.sh
|
|
4
|
-
# Apply a built method baseline bundle to a target repository.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# ./orchestration/scripts/sync-method-baseline.sh --bundle dist/platform-baseline/platform-baseline-v0.4.0.tar.gz
|
|
8
|
-
# ./orchestration/scripts/sync-method-baseline.sh --bundle ... --target-repo /path/to/repo --dry-run
|
|
9
|
-
#
|
|
10
|
-
# Multi-bundle lock model (lock_version: 3):
|
|
11
|
-
# The target repo holds one method-baseline.lock file with a `bundles:`
|
|
12
|
-
# list. Each entry records a synced bundle (forward base or additive
|
|
13
|
-
# extension) with its own managed_paths. Re-syncing a bundle replaces
|
|
14
|
-
# that bundle's entry; other bundles' entries are preserved. Removal
|
|
15
|
-
# logic is scoped per-bundle so a forward-base sync never deletes
|
|
16
|
-
# files owned by an extension bundle, and vice versa.
|
|
17
|
-
#
|
|
18
|
-
# Forward bases (platform-baseline, app-baseline, integration-baseline)
|
|
19
|
-
# set .sdd-repo-kind. Additive layers (reverse-extension-baseline) do
|
|
20
|
-
# not, and require a forward base entry already in the lock. See ADR 0135.
|
|
21
|
-
|
|
22
|
-
set -euo pipefail
|
|
23
|
-
|
|
24
|
-
RED='\033[0;31m'
|
|
25
|
-
GREEN='\033[0;32m'
|
|
26
|
-
BLUE='\033[0;34m'
|
|
27
|
-
NC='\033[0m'
|
|
28
|
-
REPO_KIND_FILE=".sdd-repo-kind"
|
|
29
|
-
PROTECTED_PREFIXES=("content/")
|
|
30
|
-
# lock_version 3 adds a per-path sha256 to each managed_paths entry
|
|
31
|
-
# (`- <digest> <path>`), so a consumer can tell "pristine but older" from
|
|
32
|
-
# "locally edited" without needing the previously-applied bundle. Bumped in
|
|
33
|
-
# lockstep with sdd-cli: a script still writing version 2 would DOWNGRADE a v3
|
|
34
|
-
# lock on its next run and destroy those digests.
|
|
35
|
-
LOCK_VERSION=3
|
|
36
|
-
|
|
37
|
-
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
38
|
-
log_fail() { echo -e "${RED}[FAIL]${NC} $1"; }
|
|
39
|
-
log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
|
|
40
|
-
log_section() {
|
|
41
|
-
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
|
|
42
|
-
echo -e "${BLUE} $1${NC}"
|
|
43
|
-
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}\n"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
usage() {
|
|
47
|
-
cat <<USAGE
|
|
48
|
-
Usage: $(basename "$0") --bundle <path.tar.gz> [--target-repo <dir>] [--dry-run] [--allow-overwrite] [--no-delete]
|
|
49
|
-
|
|
50
|
-
Options:
|
|
51
|
-
--bundle Required bundle archive generated by build-method-baseline-bundle.sh
|
|
52
|
-
--target-repo Target repository root (default: current working directory)
|
|
53
|
-
--dry-run Report plan only; do not modify files
|
|
54
|
-
--allow-overwrite Overwrite conflicting managed files (default: fail on conflict)
|
|
55
|
-
--no-delete Do not delete previously managed files removed from the incoming bundle
|
|
56
|
-
USAGE
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
BUNDLE=""
|
|
60
|
-
TARGET_REPO="$(pwd)"
|
|
61
|
-
DRY_RUN=0
|
|
62
|
-
ALLOW_OVERWRITE=0
|
|
63
|
-
NO_DELETE=0
|
|
64
|
-
|
|
65
|
-
while [[ $# -gt 0 ]]; do
|
|
66
|
-
case "$1" in
|
|
67
|
-
--bundle)
|
|
68
|
-
BUNDLE="${2:-}"
|
|
69
|
-
shift 2
|
|
70
|
-
;;
|
|
71
|
-
--target-repo)
|
|
72
|
-
TARGET_REPO="${2:-}"
|
|
73
|
-
shift 2
|
|
74
|
-
;;
|
|
75
|
-
--dry-run)
|
|
76
|
-
DRY_RUN=1
|
|
77
|
-
shift
|
|
78
|
-
;;
|
|
79
|
-
--allow-overwrite)
|
|
80
|
-
ALLOW_OVERWRITE=1
|
|
81
|
-
shift
|
|
82
|
-
;;
|
|
83
|
-
--no-delete)
|
|
84
|
-
NO_DELETE=1
|
|
85
|
-
shift
|
|
86
|
-
;;
|
|
87
|
-
-h|--help)
|
|
88
|
-
usage
|
|
89
|
-
exit 0
|
|
90
|
-
;;
|
|
91
|
-
*)
|
|
92
|
-
log_fail "Unknown argument: $1"
|
|
93
|
-
usage
|
|
94
|
-
exit 1
|
|
95
|
-
;;
|
|
96
|
-
esac
|
|
97
|
-
done
|
|
98
|
-
|
|
99
|
-
if [[ -z "$BUNDLE" ]]; then
|
|
100
|
-
log_fail "--bundle is required"
|
|
101
|
-
usage
|
|
102
|
-
exit 1
|
|
103
|
-
fi
|
|
104
|
-
|
|
105
|
-
if [[ ! -f "$BUNDLE" ]]; then
|
|
106
|
-
log_fail "Bundle not found: $BUNDLE"
|
|
107
|
-
exit 1
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
if [[ ! -d "$TARGET_REPO" ]]; then
|
|
111
|
-
log_fail "Target repo not found: $TARGET_REPO"
|
|
112
|
-
exit 1
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
log_section "Sync Method Baseline"
|
|
116
|
-
|
|
117
|
-
TMP_DIR="$(mktemp -d)"
|
|
118
|
-
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
119
|
-
|
|
120
|
-
tar -xzf "$BUNDLE" -C "$TMP_DIR"
|
|
121
|
-
BUNDLE_ROOT="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
|
122
|
-
if [[ -z "$BUNDLE_ROOT" ]]; then
|
|
123
|
-
log_fail "Could not find extracted bundle root"
|
|
124
|
-
exit 1
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
MANAGED_PATHS_FILE="$BUNDLE_ROOT/MANAGED_PATHS.txt"
|
|
128
|
-
PAYLOAD_DIR="$BUNDLE_ROOT/payload"
|
|
129
|
-
MANIFEST_FILE="$BUNDLE_ROOT/BUNDLE_MANIFEST.yaml"
|
|
130
|
-
LOCK_FILE="$TARGET_REPO/method-baseline.lock"
|
|
131
|
-
|
|
132
|
-
if [[ ! -f "$MANAGED_PATHS_FILE" || ! -d "$PAYLOAD_DIR" || ! -f "$MANIFEST_FILE" ]]; then
|
|
133
|
-
log_fail "Bundle missing required files (MANAGED_PATHS.txt, payload/, or BUNDLE_MANIFEST.yaml)"
|
|
134
|
-
exit 1
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
BUNDLE_VERSION="$(awk -F': ' '/^bundle_version:/{gsub(/"/,"",$2); print $2}' "$MANIFEST_FILE")"
|
|
138
|
-
SOURCE_COMMIT="$(awk -F': ' '/^source_commit:/{gsub(/"/,"",$2); print $2}' "$MANIFEST_FILE")"
|
|
139
|
-
BUNDLE_NAME="$(awk -F': ' '/^bundle_name:/{gsub(/"/,"",$2); print $2}' "$MANIFEST_FILE")"
|
|
140
|
-
MIN_SDD_CLI_VERSION="$(awk -F': ' '/^minimum_sdd_cli_version:/{gsub(/"/,"",$2); print $2}' "$MANIFEST_FILE")"
|
|
141
|
-
|
|
142
|
-
# If the bundle declares a minimum sdd-cli version, enforce it before
|
|
143
|
-
# applying any payload. Relevant since v0.9.0, when catalogue-schema.yaml
|
|
144
|
-
# was relocated from forward bundles to sdd-cli — adopters whose installed
|
|
145
|
-
# sdd-cli predates the relocation cannot read schema correctly.
|
|
146
|
-
if [[ -n "$MIN_SDD_CLI_VERSION" ]]; then
|
|
147
|
-
if ! command -v sdd-cli >/dev/null 2>&1; then
|
|
148
|
-
log_fail "Bundle $BUNDLE_NAME v$BUNDLE_VERSION requires sdd-cli >= $MIN_SDD_CLI_VERSION"
|
|
149
|
-
log_fail "but sdd-cli is not installed."
|
|
150
|
-
log_fail "Remediation: npm install -g @sdd-method/sdd-cli@^$MIN_SDD_CLI_VERSION"
|
|
151
|
-
exit 1
|
|
152
|
-
fi
|
|
153
|
-
INSTALLED_SDD_CLI="$(sdd-cli --version 2>/dev/null | awk '{print $NF}' | head -1)"
|
|
154
|
-
if [[ -z "$INSTALLED_SDD_CLI" ]]; then
|
|
155
|
-
log_fail "Could not determine installed sdd-cli version (sdd-cli --version returned empty)."
|
|
156
|
-
exit 1
|
|
157
|
-
fi
|
|
158
|
-
# Version sort to compare: if min comes before installed (or equals),
|
|
159
|
-
# installed satisfies the requirement.
|
|
160
|
-
LOWEST="$(printf '%s\n%s\n' "$MIN_SDD_CLI_VERSION" "$INSTALLED_SDD_CLI" | sort -V | head -1)"
|
|
161
|
-
if [[ "$LOWEST" != "$MIN_SDD_CLI_VERSION" ]]; then
|
|
162
|
-
log_fail "Bundle $BUNDLE_NAME v$BUNDLE_VERSION requires sdd-cli >= $MIN_SDD_CLI_VERSION"
|
|
163
|
-
log_fail "but installed sdd-cli is $INSTALLED_SDD_CLI."
|
|
164
|
-
log_fail "Remediation: npm install -g @sdd-method/sdd-cli@^$MIN_SDD_CLI_VERSION"
|
|
165
|
-
exit 1
|
|
166
|
-
fi
|
|
167
|
-
log_info "sdd-cli $INSTALLED_SDD_CLI satisfies bundle minimum $MIN_SDD_CLI_VERSION"
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# Bundle classification table.
|
|
171
|
-
# IS_FORWARD_BASE=1 → sets .sdd-repo-kind, qualifies as a base for additive layers
|
|
172
|
-
# REPO_KIND_VALUE → written to .sdd-repo-kind when IS_FORWARD_BASE=1
|
|
173
|
-
case "$BUNDLE_NAME" in
|
|
174
|
-
platform-baseline)
|
|
175
|
-
REPO_KIND_VALUE="platform-product"
|
|
176
|
-
IS_FORWARD_BASE=1
|
|
177
|
-
;;
|
|
178
|
-
app-baseline)
|
|
179
|
-
REPO_KIND_VALUE="application-product"
|
|
180
|
-
IS_FORWARD_BASE=1
|
|
181
|
-
;;
|
|
182
|
-
integration-baseline)
|
|
183
|
-
REPO_KIND_VALUE="integration-product"
|
|
184
|
-
IS_FORWARD_BASE=1
|
|
185
|
-
;;
|
|
186
|
-
reverse-extension-baseline)
|
|
187
|
-
REPO_KIND_VALUE=""
|
|
188
|
-
IS_FORWARD_BASE=0
|
|
189
|
-
;;
|
|
190
|
-
*)
|
|
191
|
-
log_fail "Unknown bundle_name in manifest: $BUNDLE_NAME"
|
|
192
|
-
exit 1
|
|
193
|
-
;;
|
|
194
|
-
esac
|
|
195
|
-
|
|
196
|
-
mapfile -t INCOMING_PATHS < "$MANAGED_PATHS_FILE"
|
|
197
|
-
|
|
198
|
-
for p in "${INCOMING_PATHS[@]}"; do
|
|
199
|
-
if [[ ! -f "$PAYLOAD_DIR/$p" ]]; then
|
|
200
|
-
log_fail "Missing payload file listed in MANAGED_PATHS.txt: $p"
|
|
201
|
-
exit 1
|
|
202
|
-
fi
|
|
203
|
-
for prefix in "${PROTECTED_PREFIXES[@]}"; do
|
|
204
|
-
if [[ "$p" == "$prefix"* ]]; then
|
|
205
|
-
log_fail "Incoming bundle attempts to manage protected product-owned path: $p"
|
|
206
|
-
log_fail "Method baseline bundles must not manage files under: ${PROTECTED_PREFIXES[*]}"
|
|
207
|
-
exit 1
|
|
208
|
-
fi
|
|
209
|
-
done
|
|
210
|
-
done
|
|
211
|
-
|
|
212
|
-
# ---------------------------------------------------------------------------
|
|
213
|
-
# Parse existing lock file (if any) into a working directory.
|
|
214
|
-
#
|
|
215
|
-
# $LOCK_WORK/order ordered list of bundle names
|
|
216
|
-
# $LOCK_WORK/<name>/meta key=value pairs (bundle metadata)
|
|
217
|
-
# $LOCK_WORK/<name>/paths one path per line
|
|
218
|
-
#
|
|
219
|
-
# Supports lock_version 2 and 3 (multi-bundle) and the legacy single-bundle
|
|
220
|
-
# shape (no lock_version, top-level bundle_version, flat managed_paths).
|
|
221
|
-
# Legacy locks are migrated by inferring the bundle name from
|
|
222
|
-
# .sdd-repo-kind.
|
|
223
|
-
# ---------------------------------------------------------------------------
|
|
224
|
-
|
|
225
|
-
LOCK_WORK="$TMP_DIR/lock-work"
|
|
226
|
-
mkdir -p "$LOCK_WORK"
|
|
227
|
-
: > "$LOCK_WORK/order"
|
|
228
|
-
|
|
229
|
-
infer_legacy_bundle_name() {
|
|
230
|
-
# Map .sdd-repo-kind (or legacy .iteraitive-repo-kind) → bundle name.
|
|
231
|
-
local marker="$TARGET_REPO/$REPO_KIND_FILE"
|
|
232
|
-
local legacy_marker="$TARGET_REPO/.iteraitive-repo-kind"
|
|
233
|
-
local repo_kind=""
|
|
234
|
-
if [[ -f "$marker" ]]; then
|
|
235
|
-
repo_kind="$(head -n1 "$marker" | tr -d '[:space:]')"
|
|
236
|
-
elif [[ -f "$legacy_marker" ]]; then
|
|
237
|
-
repo_kind="$(head -n1 "$legacy_marker" | tr -d '[:space:]')"
|
|
238
|
-
fi
|
|
239
|
-
case "$repo_kind" in
|
|
240
|
-
platform-product) echo "platform-baseline" ;;
|
|
241
|
-
application-product) echo "app-baseline" ;;
|
|
242
|
-
integration-product) echo "integration-baseline" ;;
|
|
243
|
-
*) echo "" ;;
|
|
244
|
-
esac
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
write_bundle_dir() {
|
|
248
|
-
# write_bundle_dir <name> <version> <source_commit> <sha> <synced_at> <is_forward_base> <paths_file>
|
|
249
|
-
local name="$1" version="$2" commit="$3" sha="$4" synced="$5" forward="$6" paths_file="$7"
|
|
250
|
-
local dir="$LOCK_WORK/$name"
|
|
251
|
-
mkdir -p "$dir"
|
|
252
|
-
{
|
|
253
|
-
echo "bundle_version=$version"
|
|
254
|
-
echo "source_commit=$commit"
|
|
255
|
-
echo "bundle_sha256=$sha"
|
|
256
|
-
echo "synced_at_utc=$synced"
|
|
257
|
-
echo "is_forward_base=$forward"
|
|
258
|
-
} > "$dir/meta"
|
|
259
|
-
if [[ -n "$paths_file" && -f "$paths_file" ]]; then
|
|
260
|
-
cp "$paths_file" "$dir/paths"
|
|
261
|
-
else
|
|
262
|
-
: > "$dir/paths"
|
|
263
|
-
fi
|
|
264
|
-
# Record order if not already present.
|
|
265
|
-
if ! grep -qFx "$name" "$LOCK_WORK/order"; then
|
|
266
|
-
echo "$name" >> "$LOCK_WORK/order"
|
|
267
|
-
fi
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if [[ -f "$LOCK_FILE" ]]; then
|
|
271
|
-
# Detect shape: new locks have a `lock_version:` line near the top.
|
|
272
|
-
if grep -q '^lock_version:' "$LOCK_FILE"; then
|
|
273
|
-
# New shape — parse bundle blocks.
|
|
274
|
-
awk '
|
|
275
|
-
function flush_bundle() {
|
|
276
|
-
if (cur_name != "") {
|
|
277
|
-
print "BUNDLE_END"
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
BEGIN { in_bundles = 0; cur_name = ""; in_paths = 0 }
|
|
281
|
-
/^bundles:[[:space:]]*$/ { in_bundles = 1; next }
|
|
282
|
-
in_bundles && /^[^[:space:]]/ { flush_bundle(); in_bundles = 0; next }
|
|
283
|
-
in_bundles && /^ - name:/ {
|
|
284
|
-
flush_bundle()
|
|
285
|
-
sub(/^ - name:[[:space:]]*/, "", $0)
|
|
286
|
-
gsub(/"/, "", $0)
|
|
287
|
-
cur_name = $0
|
|
288
|
-
in_paths = 0
|
|
289
|
-
print "BUNDLE_BEGIN " cur_name
|
|
290
|
-
next
|
|
291
|
-
}
|
|
292
|
-
in_bundles && cur_name != "" && /^ managed_paths:[[:space:]]*$/ {
|
|
293
|
-
in_paths = 1
|
|
294
|
-
next
|
|
295
|
-
}
|
|
296
|
-
in_bundles && cur_name != "" && in_paths && /^ - / {
|
|
297
|
-
sub(/^ - /, "", $0)
|
|
298
|
-
print "PATH " $0
|
|
299
|
-
next
|
|
300
|
-
}
|
|
301
|
-
in_bundles && cur_name != "" && /^ [a-z0-9_]+:/ {
|
|
302
|
-
in_paths = 0
|
|
303
|
-
line = $0
|
|
304
|
-
sub(/^ /, "", line)
|
|
305
|
-
# Strip surrounding quotes from the value portion only. Note: the
|
|
306
|
-
# value may itself contain colons (e.g. ISO timestamps), so we
|
|
307
|
-
# split on the FIRST colon only, not all of them.
|
|
308
|
-
n = index(line, ":")
|
|
309
|
-
key = substr(line, 1, n - 1)
|
|
310
|
-
val = substr(line, n + 1)
|
|
311
|
-
sub(/^[[:space:]]+/, "", val)
|
|
312
|
-
gsub(/"/, "", val)
|
|
313
|
-
print "META " key " " val
|
|
314
|
-
next
|
|
315
|
-
}
|
|
316
|
-
END { flush_bundle() }
|
|
317
|
-
' "$LOCK_FILE" > "$TMP_DIR/lock-parsed.txt"
|
|
318
|
-
|
|
319
|
-
cur_name=""
|
|
320
|
-
cur_version=""; cur_commit=""; cur_sha=""; cur_synced=""; cur_forward=""
|
|
321
|
-
cur_paths_file=""
|
|
322
|
-
while IFS= read -r line; do
|
|
323
|
-
case "$line" in
|
|
324
|
-
BUNDLE_BEGIN\ *)
|
|
325
|
-
cur_name="${line#BUNDLE_BEGIN }"
|
|
326
|
-
cur_version=""; cur_commit=""; cur_sha=""; cur_synced=""; cur_forward="false"
|
|
327
|
-
cur_paths_file="$TMP_DIR/lock-paths-$cur_name"
|
|
328
|
-
: > "$cur_paths_file"
|
|
329
|
-
;;
|
|
330
|
-
META\ bundle_version\ *) cur_version="${line#META bundle_version }" ;;
|
|
331
|
-
META\ source_commit\ *) cur_commit="${line#META source_commit }" ;;
|
|
332
|
-
META\ bundle_sha256\ *) cur_sha="${line#META bundle_sha256 }" ;;
|
|
333
|
-
META\ synced_at_utc\ *) cur_synced="${line#META synced_at_utc }" ;;
|
|
334
|
-
META\ is_forward_base\ *) cur_forward="${line#META is_forward_base }" ;;
|
|
335
|
-
PATH\ *) echo "${line#PATH }" >> "$cur_paths_file" ;;
|
|
336
|
-
BUNDLE_END)
|
|
337
|
-
if [[ -n "$cur_name" ]]; then
|
|
338
|
-
write_bundle_dir "$cur_name" "$cur_version" "$cur_commit" "$cur_sha" "$cur_synced" "$cur_forward" "$cur_paths_file"
|
|
339
|
-
fi
|
|
340
|
-
cur_name=""
|
|
341
|
-
;;
|
|
342
|
-
esac
|
|
343
|
-
done < "$TMP_DIR/lock-parsed.txt"
|
|
344
|
-
else
|
|
345
|
-
# Legacy shape — single bundle, top-level metadata.
|
|
346
|
-
legacy_name="$(infer_legacy_bundle_name)"
|
|
347
|
-
if [[ -z "$legacy_name" ]]; then
|
|
348
|
-
log_fail "Legacy lock detected but cannot infer bundle name from $REPO_KIND_FILE."
|
|
349
|
-
log_fail "Expected one of: platform-product, application-product, integration-product."
|
|
350
|
-
exit 1
|
|
351
|
-
fi
|
|
352
|
-
legacy_version="$(awk -F': ' '/^bundle_version:/{gsub(/"/,"",$2); print $2}' "$LOCK_FILE")"
|
|
353
|
-
legacy_commit="$(awk -F': ' '/^source_commit:/{gsub(/"/,"",$2); print $2}' "$LOCK_FILE")"
|
|
354
|
-
legacy_sha="$(awk -F': ' '/^bundle_sha256:/{gsub(/"/,"",$2); print $2}' "$LOCK_FILE")"
|
|
355
|
-
legacy_synced="$(awk -F': ' '/^synced_at_utc:/{gsub(/"/,"",$2); print $2}' "$LOCK_FILE")"
|
|
356
|
-
legacy_paths="$TMP_DIR/legacy-paths"
|
|
357
|
-
awk '
|
|
358
|
-
/^managed_paths:/ {in_list=1; next}
|
|
359
|
-
in_list && /^ - / {sub(/^ - /, ""); print; next}
|
|
360
|
-
in_list && !/^ - / {in_list=0}
|
|
361
|
-
' "$LOCK_FILE" > "$legacy_paths"
|
|
362
|
-
write_bundle_dir "$legacy_name" "$legacy_version" "$legacy_commit" "$legacy_sha" "$legacy_synced" "true" "$legacy_paths"
|
|
363
|
-
log_info "Migrated legacy single-bundle lock → multi-bundle (lock_version: $LOCK_VERSION). Inferred bundle: $legacy_name"
|
|
364
|
-
fi
|
|
365
|
-
fi
|
|
366
|
-
|
|
367
|
-
# ---------------------------------------------------------------------------
|
|
368
|
-
# Validate protected prefixes against ALL previously-locked paths.
|
|
369
|
-
# ---------------------------------------------------------------------------
|
|
370
|
-
if [[ -d "$LOCK_WORK" && -s "$LOCK_WORK/order" ]]; then
|
|
371
|
-
while IFS= read -r b; do
|
|
372
|
-
if [[ -f "$LOCK_WORK/$b/paths" ]]; then
|
|
373
|
-
while IFS= read -r p; do
|
|
374
|
-
for prefix in "${PROTECTED_PREFIXES[@]}"; do
|
|
375
|
-
if [[ "$p" == "$prefix"* ]]; then
|
|
376
|
-
log_fail "Existing lock contains protected product-owned path: $p (bundle: $b)"
|
|
377
|
-
log_fail "Resolve this manually before refreshing the method baseline."
|
|
378
|
-
exit 1
|
|
379
|
-
fi
|
|
380
|
-
done
|
|
381
|
-
done < "$LOCK_WORK/$b/paths"
|
|
382
|
-
fi
|
|
383
|
-
done < "$LOCK_WORK/order"
|
|
384
|
-
fi
|
|
385
|
-
|
|
386
|
-
# ---------------------------------------------------------------------------
|
|
387
|
-
# requires_forward_base check for additive bundles.
|
|
388
|
-
# An additive bundle (e.g. reverse-extension-baseline) requires that the
|
|
389
|
-
# target repo already have at least one forward-base bundle in its lock.
|
|
390
|
-
# ---------------------------------------------------------------------------
|
|
391
|
-
if [[ "$IS_FORWARD_BASE" -eq 0 ]]; then
|
|
392
|
-
has_forward_base=0
|
|
393
|
-
if [[ -s "$LOCK_WORK/order" ]]; then
|
|
394
|
-
while IFS= read -r b; do
|
|
395
|
-
if [[ "$b" == "$BUNDLE_NAME" ]]; then continue; fi
|
|
396
|
-
if [[ -f "$LOCK_WORK/$b/meta" ]] && grep -q '^is_forward_base=true$' "$LOCK_WORK/$b/meta"; then
|
|
397
|
-
has_forward_base=1
|
|
398
|
-
break
|
|
399
|
-
fi
|
|
400
|
-
done < "$LOCK_WORK/order"
|
|
401
|
-
fi
|
|
402
|
-
if [[ "$has_forward_base" -eq 0 ]]; then
|
|
403
|
-
log_fail "$BUNDLE_NAME is an additive extension. Sync a forward base first."
|
|
404
|
-
log_fail "Forward bases: platform-baseline, app-baseline, integration-baseline."
|
|
405
|
-
exit 1
|
|
406
|
-
fi
|
|
407
|
-
fi
|
|
408
|
-
|
|
409
|
-
# ---------------------------------------------------------------------------
|
|
410
|
-
# ADR transitions (rename / withdrawal / reassignment).
|
|
411
|
-
#
|
|
412
|
-
# Compare the bundle's ADR_TRANSITIONS.yaml against the previously-applied
|
|
413
|
-
# bundle_version recorded in the lock for THIS bundle. Apply transitions
|
|
414
|
-
# whose introduced_in_version is greater than the previous version.
|
|
415
|
-
# ---------------------------------------------------------------------------
|
|
416
|
-
ADR_TRANSITIONS_FILE="$BUNDLE_ROOT/ADR_TRANSITIONS.yaml"
|
|
417
|
-
RENAMES_TO_APPLY=()
|
|
418
|
-
TRANSITION_WARNINGS=()
|
|
419
|
-
|
|
420
|
-
if [[ -f "$ADR_TRANSITIONS_FILE" ]]; then
|
|
421
|
-
if ! command -v yq >/dev/null 2>&1; then
|
|
422
|
-
log_info "yq not available; skipping ADR transitions (install mikefarah/yq to enable)"
|
|
423
|
-
else
|
|
424
|
-
PREV_VERSION=""
|
|
425
|
-
if [[ -f "$LOCK_WORK/$BUNDLE_NAME/meta" ]]; then
|
|
426
|
-
PREV_VERSION="$(awk -F= '/^bundle_version=/{print $2}' "$LOCK_WORK/$BUNDLE_NAME/meta")"
|
|
427
|
-
fi
|
|
428
|
-
|
|
429
|
-
semver_lt() {
|
|
430
|
-
# semver_lt A B → exit 0 if A < B. Normalise a leading 'v': the lock
|
|
431
|
-
# records bundle_version as "v1.2.0" but ADR_TRANSITIONS.yaml records
|
|
432
|
-
# introduced_in_version as "1.3.0"; without this, sort -V mis-orders the
|
|
433
|
-
# v-prefixed operand and every rename transition is silently skipped.
|
|
434
|
-
local a="${1#v}" b="${2#v}"
|
|
435
|
-
[[ "$a" == "$b" ]] && return 1
|
|
436
|
-
[[ "$(printf '%s\n%s\n' "$a" "$b" | sort -V | head -n1)" == "$a" ]]
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
TRANSITION_COUNT="$(yq e '.transitions | length' "$ADR_TRANSITIONS_FILE")"
|
|
440
|
-
for ((i=0; i<TRANSITION_COUNT; i++)); do
|
|
441
|
-
kind="$(yq e ".transitions[$i].kind" "$ADR_TRANSITIONS_FILE")"
|
|
442
|
-
adr_id="$(yq e ".transitions[$i].adr_id" "$ADR_TRANSITIONS_FILE")"
|
|
443
|
-
introduced="$(yq e ".transitions[$i].introduced_in_version" "$ADR_TRANSITIONS_FILE")"
|
|
444
|
-
|
|
445
|
-
# Only apply transitions newer than the previously synced version.
|
|
446
|
-
if [[ -n "$PREV_VERSION" ]] && ! semver_lt "$PREV_VERSION" "$introduced"; then
|
|
447
|
-
continue
|
|
448
|
-
fi
|
|
449
|
-
|
|
450
|
-
case "$kind" in
|
|
451
|
-
rename)
|
|
452
|
-
from_path="$(yq e ".transitions[$i].from_path" "$ADR_TRANSITIONS_FILE")"
|
|
453
|
-
to_path="$(yq e ".transitions[$i].to_path" "$ADR_TRANSITIONS_FILE")"
|
|
454
|
-
if [[ -f "$TARGET_REPO/$from_path" && -f "$TARGET_REPO/$to_path" ]]; then
|
|
455
|
-
TRANSITION_WARNINGS+=("[ADR $adr_id rename] both paths exist in adopter repo; manual review needed: $from_path → $to_path")
|
|
456
|
-
elif [[ -f "$TARGET_REPO/$from_path" ]]; then
|
|
457
|
-
RENAMES_TO_APPLY+=("$from_path|$to_path|$adr_id")
|
|
458
|
-
fi
|
|
459
|
-
;;
|
|
460
|
-
withdrawal)
|
|
461
|
-
withdrawn_path="$(yq e ".transitions[$i].withdrawn_path" "$ADR_TRANSITIONS_FILE")"
|
|
462
|
-
adopter_action="$(yq e ".transitions[$i].adopter_action" "$ADR_TRANSITIONS_FILE")"
|
|
463
|
-
if [[ -f "$TARGET_REPO/$withdrawn_path" ]]; then
|
|
464
|
-
TRANSITION_WARNINGS+=("[ADR $adr_id withdrawal] $withdrawn_path is still present. $adopter_action")
|
|
465
|
-
fi
|
|
466
|
-
;;
|
|
467
|
-
reassignment)
|
|
468
|
-
new_decision="$(yq e ".transitions[$i].new_decision_path" "$ADR_TRANSITIONS_FILE")"
|
|
469
|
-
TRANSITION_WARNINGS+=("[ADR $adr_id reassignment] slot now points to a different decision: $new_decision")
|
|
470
|
-
;;
|
|
471
|
-
esac
|
|
472
|
-
done
|
|
473
|
-
fi
|
|
474
|
-
fi
|
|
475
|
-
|
|
476
|
-
# ---------------------------------------------------------------------------
|
|
477
|
-
# Conflicts: incoming files that exist in the target with different content.
|
|
478
|
-
# ---------------------------------------------------------------------------
|
|
479
|
-
CONFLICTS=()
|
|
480
|
-
for p in "${INCOMING_PATHS[@]}"; do
|
|
481
|
-
target_file="$TARGET_REPO/$p"
|
|
482
|
-
incoming_file="$PAYLOAD_DIR/$p"
|
|
483
|
-
if [[ -f "$target_file" ]] && ! cmp -s "$incoming_file" "$target_file"; then
|
|
484
|
-
CONFLICTS+=("$p")
|
|
485
|
-
fi
|
|
486
|
-
done
|
|
487
|
-
|
|
488
|
-
if [[ ${#CONFLICTS[@]} -gt 0 && "$ALLOW_OVERWRITE" -ne 1 ]]; then
|
|
489
|
-
log_fail "Found ${#CONFLICTS[@]} conflicting managed file(s). Re-run with --allow-overwrite to apply."
|
|
490
|
-
printf '%s\n' "${CONFLICTS[@]}" | sed 's/^/ - /'
|
|
491
|
-
exit 1
|
|
492
|
-
fi
|
|
493
|
-
|
|
494
|
-
# Cross-bundle ownership conflict: a path managed by another bundle in the
|
|
495
|
-
# lock cannot be claimed by this bundle. Sync must remain federated.
|
|
496
|
-
if [[ -s "$LOCK_WORK/order" ]]; then
|
|
497
|
-
while IFS= read -r b; do
|
|
498
|
-
if [[ "$b" == "$BUNDLE_NAME" ]]; then continue; fi
|
|
499
|
-
if [[ -f "$LOCK_WORK/$b/paths" ]]; then
|
|
500
|
-
for p in "${INCOMING_PATHS[@]}"; do
|
|
501
|
-
if grep -qFx "$p" "$LOCK_WORK/$b/paths"; then
|
|
502
|
-
log_fail "Path '$p' is already managed by bundle '$b'; cannot also be claimed by '$BUNDLE_NAME'."
|
|
503
|
-
log_fail "Method baselines must not overlap. Fix upstream and rebuild."
|
|
504
|
-
exit 1
|
|
505
|
-
fi
|
|
506
|
-
done
|
|
507
|
-
fi
|
|
508
|
-
done < "$LOCK_WORK/order"
|
|
509
|
-
fi
|
|
510
|
-
|
|
511
|
-
# ---------------------------------------------------------------------------
|
|
512
|
-
# Removals: paths previously managed by THIS bundle that are no longer in
|
|
513
|
-
# the incoming list.
|
|
514
|
-
# ---------------------------------------------------------------------------
|
|
515
|
-
REMOVE_PATHS=()
|
|
516
|
-
PREV_BUNDLE_PATHS_FILE=""
|
|
517
|
-
if [[ -f "$LOCK_WORK/$BUNDLE_NAME/paths" ]]; then
|
|
518
|
-
PREV_BUNDLE_PATHS_FILE="$LOCK_WORK/$BUNDLE_NAME/paths"
|
|
519
|
-
fi
|
|
520
|
-
|
|
521
|
-
if [[ "$NO_DELETE" -ne 1 && -n "$PREV_BUNDLE_PATHS_FILE" && -s "$PREV_BUNDLE_PATHS_FILE" ]]; then
|
|
522
|
-
while IFS= read -r prev; do
|
|
523
|
-
keep=0
|
|
524
|
-
for cur in "${INCOMING_PATHS[@]}"; do
|
|
525
|
-
if [[ "$prev" == "$cur" ]]; then
|
|
526
|
-
keep=1
|
|
527
|
-
break
|
|
528
|
-
fi
|
|
529
|
-
done
|
|
530
|
-
if [[ "$keep" -eq 0 ]]; then
|
|
531
|
-
REMOVE_PATHS+=("$prev")
|
|
532
|
-
fi
|
|
533
|
-
done < "$PREV_BUNDLE_PATHS_FILE"
|
|
534
|
-
fi
|
|
535
|
-
|
|
536
|
-
log_info "Bundle: $BUNDLE_NAME (version: $BUNDLE_VERSION)"
|
|
537
|
-
log_info "Incoming managed files: ${#INCOMING_PATHS[@]}"
|
|
538
|
-
log_info "Conflicts: ${#CONFLICTS[@]}"
|
|
539
|
-
log_info "Removals from previous sync of this bundle: ${#REMOVE_PATHS[@]}"
|
|
540
|
-
log_info "ADR renames to apply: ${#RENAMES_TO_APPLY[@]}"
|
|
541
|
-
log_info "ADR transition warnings: ${#TRANSITION_WARNINGS[@]}"
|
|
542
|
-
log_info "Protected ownership prefixes: ${PROTECTED_PREFIXES[*]}"
|
|
543
|
-
|
|
544
|
-
if [[ ${#TRANSITION_WARNINGS[@]} -gt 0 ]]; then
|
|
545
|
-
log_info "ADR transition notices:"
|
|
546
|
-
printf '%s\n' "${TRANSITION_WARNINGS[@]}" | sed 's/^/ ! /'
|
|
547
|
-
fi
|
|
548
|
-
|
|
549
|
-
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
550
|
-
log_info "Dry run enabled. No files modified."
|
|
551
|
-
if [[ ${#CONFLICTS[@]} -gt 0 ]]; then
|
|
552
|
-
log_info "Conflicting files:"
|
|
553
|
-
printf '%s\n' "${CONFLICTS[@]}" | sed 's/^/ - /'
|
|
554
|
-
fi
|
|
555
|
-
if [[ ${#REMOVE_PATHS[@]} -gt 0 ]]; then
|
|
556
|
-
log_info "Files that would be removed:"
|
|
557
|
-
printf '%s\n' "${REMOVE_PATHS[@]}" | sed 's/^/ - /'
|
|
558
|
-
fi
|
|
559
|
-
if [[ ${#RENAMES_TO_APPLY[@]} -gt 0 ]]; then
|
|
560
|
-
log_info "ADR renames that would be applied:"
|
|
561
|
-
for r in "${RENAMES_TO_APPLY[@]}"; do
|
|
562
|
-
from="${r%%|*}"; rest="${r#*|}"; to="${rest%%|*}"; aid="${rest##*|}"
|
|
563
|
-
echo " - [ADR $aid] $from → $to"
|
|
564
|
-
done
|
|
565
|
-
fi
|
|
566
|
-
exit 0
|
|
567
|
-
fi
|
|
568
|
-
|
|
569
|
-
# ---------------------------------------------------------------------------
|
|
570
|
-
# Apply ADR renames BEFORE the file copy phase so the new path doesn't
|
|
571
|
-
# collide with the old one when both are tracked in git.
|
|
572
|
-
# ---------------------------------------------------------------------------
|
|
573
|
-
for r in "${RENAMES_TO_APPLY[@]}"; do
|
|
574
|
-
from="${r%%|*}"; rest="${r#*|}"; to="${rest%%|*}"; aid="${rest##*|}"
|
|
575
|
-
mkdir -p "$TARGET_REPO/$(dirname "$to")"
|
|
576
|
-
if [[ -d "$TARGET_REPO/.git" ]] && git -C "$TARGET_REPO" ls-files --error-unmatch "$from" >/dev/null 2>&1; then
|
|
577
|
-
git -C "$TARGET_REPO" mv "$from" "$to"
|
|
578
|
-
log_info "Renamed (git mv) [ADR $aid]: $from → $to"
|
|
579
|
-
else
|
|
580
|
-
mv "$TARGET_REPO/$from" "$TARGET_REPO/$to"
|
|
581
|
-
log_info "Renamed [ADR $aid]: $from → $to"
|
|
582
|
-
fi
|
|
583
|
-
done
|
|
584
|
-
|
|
585
|
-
# ---------------------------------------------------------------------------
|
|
586
|
-
# Apply: copy incoming files, remove stale ones.
|
|
587
|
-
# ---------------------------------------------------------------------------
|
|
588
|
-
for p in "${INCOMING_PATHS[@]}"; do
|
|
589
|
-
mkdir -p "$TARGET_REPO/$(dirname "$p")"
|
|
590
|
-
cp "$PAYLOAD_DIR/$p" "$TARGET_REPO/$p"
|
|
591
|
-
done
|
|
592
|
-
|
|
593
|
-
for p in "${REMOVE_PATHS[@]}"; do
|
|
594
|
-
rm -f "$TARGET_REPO/$p"
|
|
595
|
-
done
|
|
596
|
-
|
|
597
|
-
# Forward bases write the repo-kind marker; additive layers don't touch it.
|
|
598
|
-
if [[ "$IS_FORWARD_BASE" -eq 1 ]]; then
|
|
599
|
-
printf '%s\n' "$REPO_KIND_VALUE" > "$TARGET_REPO/$REPO_KIND_FILE"
|
|
600
|
-
fi
|
|
601
|
-
|
|
602
|
-
# ---------------------------------------------------------------------------
|
|
603
|
-
# Update lock work-dir for this bundle, then emit merged lock file.
|
|
604
|
-
# ---------------------------------------------------------------------------
|
|
605
|
-
SYNCED_AT_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
606
|
-
BUNDLE_SHA="$(sha256sum "$BUNDLE" | awk '{print $1}')"
|
|
607
|
-
INCOMING_PATHS_FILE="$TMP_DIR/incoming-paths.txt"
|
|
608
|
-
printf '%s\n' "${INCOMING_PATHS[@]}" > "$INCOMING_PATHS_FILE"
|
|
609
|
-
if [[ "$IS_FORWARD_BASE" -eq 1 ]]; then
|
|
610
|
-
forward_str="true"
|
|
611
|
-
else
|
|
612
|
-
forward_str="false"
|
|
613
|
-
fi
|
|
614
|
-
write_bundle_dir "$BUNDLE_NAME" "$BUNDLE_VERSION" "$SOURCE_COMMIT" "$BUNDLE_SHA" "$SYNCED_AT_UTC" "$forward_str" "$INCOMING_PATHS_FILE"
|
|
615
|
-
|
|
616
|
-
{
|
|
617
|
-
echo "method_repo: sdd-method"
|
|
618
|
-
echo "lock_version: $LOCK_VERSION"
|
|
619
|
-
echo "bundles:"
|
|
620
|
-
while IFS= read -r b; do
|
|
621
|
-
[[ -z "$b" ]] && continue
|
|
622
|
-
meta="$LOCK_WORK/$b/meta"
|
|
623
|
-
paths="$LOCK_WORK/$b/paths"
|
|
624
|
-
bv="$(grep '^bundle_version=' "$meta" | cut -d= -f2-)"
|
|
625
|
-
sc="$(grep '^source_commit=' "$meta" | cut -d= -f2-)"
|
|
626
|
-
bsh="$(grep '^bundle_sha256=' "$meta" | cut -d= -f2-)"
|
|
627
|
-
sa="$(grep '^synced_at_utc=' "$meta" | cut -d= -f2-)"
|
|
628
|
-
fb="$(grep '^is_forward_base=' "$meta" | cut -d= -f2-)"
|
|
629
|
-
echo " - name: $b"
|
|
630
|
-
echo " bundle_version: \"$bv\""
|
|
631
|
-
echo " source_commit: \"$sc\""
|
|
632
|
-
echo " bundle_sha256: \"$bsh\""
|
|
633
|
-
echo " synced_at_utc: \"$sa\""
|
|
634
|
-
echo " is_forward_base: $fb"
|
|
635
|
-
echo " managed_paths:"
|
|
636
|
-
if [[ -s "$paths" ]]; then
|
|
637
|
-
while IFS= read -r p; do
|
|
638
|
-
# lock_version 3: entries are `<sha256> <path>`. For the bundle being
|
|
639
|
-
# synced the digest is computed from the PAYLOAD — what this run applies.
|
|
640
|
-
# Entries for OTHER bundles were parsed out of the existing lock as opaque
|
|
641
|
-
# strings, so any digest they already carry round-trips untouched; they are
|
|
642
|
-
# re-emitted verbatim rather than re-hashed against a payload this run does
|
|
643
|
-
# not have.
|
|
644
|
-
if [[ "$b" == "$BUNDLE_NAME" && "$p" != *" "* && -f "$PAYLOAD_DIR/$p" ]]; then
|
|
645
|
-
echo " - $(sha256sum "$PAYLOAD_DIR/$p" | awk '{print $1}') $p"
|
|
646
|
-
else
|
|
647
|
-
echo " - $p"
|
|
648
|
-
fi
|
|
649
|
-
done < "$paths"
|
|
650
|
-
fi
|
|
651
|
-
done < "$LOCK_WORK/order"
|
|
652
|
-
} > "$LOCK_FILE"
|
|
653
|
-
|
|
654
|
-
log_pass "Method baseline synced to: $TARGET_REPO"
|
|
655
|
-
log_info "Updated lock file: method-baseline.lock (lock_version: $LOCK_VERSION)"
|
|
656
|
-
if [[ "$IS_FORWARD_BASE" -eq 1 ]]; then
|
|
657
|
-
log_info "Set repo kind marker: $REPO_KIND_FILE = $REPO_KIND_VALUE"
|
|
658
|
-
else
|
|
659
|
-
log_info "Additive layer applied; .sdd-repo-kind unchanged."
|
|
660
|
-
fi
|