aw-spec-cli 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +291 -0
- package/assets/docs/UI.Visualization/PACKAGING.md +191 -0
- package/assets/docs/UI.Visualization/UI.Visualization.Reference.pack.txt +11768 -0
- package/assets/docs/UI.Visualization/UI.Visualization.pack.txt +11214 -0
- package/assets/docs/UI.Visualization/pack-foundation.ps1 +165 -0
- package/assets/docs/UI.Visualization/pack-foundation.sh +206 -0
- package/assets/docs/UI.Visualization/pack-reference-foundation.ps1 +159 -0
- package/assets/docs/UI.Visualization/pack-reference-foundation.sh +205 -0
- package/assets/docs/UI.Visualization/unpack-foundation.ps1 +126 -0
- package/assets/docs/UI.Visualization/unpack-foundation.sh +217 -0
- package/assets/docs/UI.Visualization/unpack-reference-foundation.ps1 +129 -0
- package/assets/docs/UI.Visualization/unpack-reference-foundation.sh +218 -0
- package/assets/docs/domainspecs/UIVisualization/chart-bubble.md +70 -0
- package/assets/docs/domainspecs/UIVisualization/chart-dimred.md +58 -0
- package/assets/docs/domainspecs/UIVisualization/chart-heatmap.md +62 -0
- package/assets/docs/domainspecs/UIVisualization/chart-network.md +69 -0
- package/assets/docs/domainspecs/UIVisualization/chart-parallel.md +75 -0
- package/assets/docs/domainspecs/UIVisualization/chart-scurve.md +66 -0
- package/assets/docs/domainspecs/UIVisualization/chart-survival.md +69 -0
- package/assets/docs/domainspecs/UIVisualization/chart-trellis.md +68 -0
- package/assets/docs/domainspecs/UIVisualization/chart-violin.md +61 -0
- package/assets/docs/domainspecs/UIVisualization/chart-volcano.md +63 -0
- package/assets/docs/domainspecs/UIVisualization/charting-standards.domain.md +80 -0
- package/assets/docs/domainspecs/UIVisualization/clean-architecture.domain.md +87 -0
- package/assets/docs/domainspecs/UIVisualization/coding-standards.domain.md +47 -0
- package/assets/docs/domainspecs/UIVisualization/comandrulemap.md +130 -0
- package/assets/docs/domainspecs/UIVisualization/component-contracts.domain.md +67 -0
- package/assets/docs/domainspecs/UIVisualization/data-source.domain.md +68 -0
- package/assets/docs/domainspecs/UIVisualization/domain-requirements.md +110 -0
- package/assets/docs/domainspecs/UIVisualization/folder-structure.domain.md +55 -0
- package/assets/docs/domainspecs/UIVisualization/state-management.domain.md +67 -0
- package/assets/docs/domainspecs/UIVisualization/testing.domain.md +52 -0
- package/assets/docs/domainspecs/UIVisualization/ui-viz-terms.md +39 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +54 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +142 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/manifest.d.ts +31 -0
- package/dist/utils/manifest.d.ts.map +1 -0
- package/dist/utils/manifest.js +91 -0
- package/dist/utils/manifest.js.map +1 -0
- package/dist/utils/update-engine.d.ts +52 -0
- package/dist/utils/update-engine.d.ts.map +1 -0
- package/dist/utils/update-engine.js +203 -0
- package/dist/utils/update-engine.js.map +1 -0
- package/package.json +1 -1
- package/assets/docs/requirement-spec.txt +0 -15
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# pack-reference-foundation.sh — bash/macOS port of pack-reference-foundation.ps1
|
|
4
|
+
#
|
|
5
|
+
# SYNOPSIS
|
|
6
|
+
# Pack a verified UI feature reference template into a single AW-Spec text pack file.
|
|
7
|
+
#
|
|
8
|
+
# DESCRIPTION
|
|
9
|
+
# Walks --source-dir (a templates/features/<Name>.SimpleSample folder for UI features),
|
|
10
|
+
# excludes build/IDE artifacts, JS output, template meta-files, and secret-bearing files,
|
|
11
|
+
# and writes every remaining file into one pack file using <<<FILE>>> / <<<ENDFILE>>>
|
|
12
|
+
# markers. Format-compatible with unpack-reference-foundation.
|
|
13
|
+
#
|
|
14
|
+
# EXAMPLE
|
|
15
|
+
# ./pack-reference-foundation.sh \
|
|
16
|
+
# --source-dir ../../../../templates/features/UIVisualization.SimpleSample \
|
|
17
|
+
# --out-file ./UI.Visualization.Reference.pack.txt \
|
|
18
|
+
# --sentinel UI.Visualization \
|
|
19
|
+
# --foundation UI.Visualization.Reference
|
|
20
|
+
#
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------------ defaults ---
|
|
24
|
+
SENTINEL='AppWeaver.UI.Visualization'
|
|
25
|
+
FOUNDATION='AppWeaver.UI.Visualization.Reference'
|
|
26
|
+
SOURCE_DIR=''
|
|
27
|
+
OUT_FILE=''
|
|
28
|
+
ALLOW_SECRETS=0
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat >&2 <<'EOF'
|
|
32
|
+
Usage: pack-reference-foundation.sh --source-dir <dir> [--out-file <file>]
|
|
33
|
+
[--sentinel <name>] [--foundation <name>]
|
|
34
|
+
[--allow-secrets]
|
|
35
|
+
|
|
36
|
+
-s, --source-dir <dir> templates/features/<Name>.SimpleSample folder (required)
|
|
37
|
+
-o, --out-file <file> Output pack file (default: ./<foundation>.pack.txt)
|
|
38
|
+
--sentinel <name> Sentinel app name (default: UI.Visualization)
|
|
39
|
+
--foundation <name> Foundation name (default: UI.Visualization.Reference)
|
|
40
|
+
--allow-secrets Pack even if credential markers are detected
|
|
41
|
+
-h, --help Show this help
|
|
42
|
+
|
|
43
|
+
Positional fallback: pack-reference-foundation.sh <source-dir> [out-file]
|
|
44
|
+
EOF
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# -------------------------------------------------------------- arg parsing ---
|
|
48
|
+
while [ $# -gt 0 ]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
-s|--source-dir) SOURCE_DIR="${2:?--source-dir needs a value}"; shift 2 ;;
|
|
51
|
+
-o|--out-file) OUT_FILE="${2:?--out-file needs a value}"; shift 2 ;;
|
|
52
|
+
--sentinel) SENTINEL="${2:?--sentinel needs a value}"; shift 2 ;;
|
|
53
|
+
--foundation) FOUNDATION="${2:?--foundation needs a value}"; shift 2 ;;
|
|
54
|
+
--allow-secrets) ALLOW_SECRETS=1; shift ;;
|
|
55
|
+
-h|--help) usage; exit 0 ;;
|
|
56
|
+
--) shift; break ;;
|
|
57
|
+
-*) echo "Error: unknown option: $1" >&2; usage; exit 2 ;;
|
|
58
|
+
*)
|
|
59
|
+
if [ -z "$SOURCE_DIR" ]; then SOURCE_DIR="$1"
|
|
60
|
+
elif [ -z "$OUT_FILE" ]; then OUT_FILE="$1"
|
|
61
|
+
else echo "Error: unexpected argument: $1" >&2; usage; exit 2
|
|
62
|
+
fi
|
|
63
|
+
shift ;;
|
|
64
|
+
esac
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
[ -n "$SOURCE_DIR" ] || { echo "Error: --source-dir is required." >&2; usage; exit 2; }
|
|
68
|
+
[ -d "$SOURCE_DIR" ] || { echo "Error: SourceDir is not a folder: $SOURCE_DIR" >&2; exit 1; }
|
|
69
|
+
|
|
70
|
+
SRC="$(cd "$SOURCE_DIR" && pwd -P)"
|
|
71
|
+
[ -n "$OUT_FILE" ] || OUT_FILE="$(pwd)/$FOUNDATION.pack.txt"
|
|
72
|
+
|
|
73
|
+
# -------------------------------------------------------------- exclusions ---
|
|
74
|
+
EXCLUDE_DIRS=(
|
|
75
|
+
bin obj .vs .git .idea .vscode TestResults
|
|
76
|
+
node_modules packages App_Data artifacts
|
|
77
|
+
.next .turbo dist out coverage .cache logs
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
EXCLUDE_FILES=(
|
|
81
|
+
'*.user' '*.suo' '*.DotSettings.user' '*.db' '*.db-shm' '*.db-wal' '*.sqlite' '*.log'
|
|
82
|
+
'.env' '*.env.*' '.env.*' '*.pfx' '*.pem' '*.key' '*.snk' 'id_rsa' 'id_rsa.pub' 'secrets.json'
|
|
83
|
+
'.DS_Store'
|
|
84
|
+
'*.my-ref.*'
|
|
85
|
+
'*TemplateConfig.config'
|
|
86
|
+
'*.tsbuildinfo'
|
|
87
|
+
'next-env.d.ts'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
SECRET_NAMES=('NuGet ClearTextPassword' 'Private key block' 'AWS access key id' 'Azure DevOps PAT-ish')
|
|
91
|
+
SECRET_RX=(
|
|
92
|
+
'ClearTextPassword'
|
|
93
|
+
'BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY'
|
|
94
|
+
'AKIA[0-9A-Z]{16}'
|
|
95
|
+
'[A-Za-z0-9]{52}AZDO[A-Za-z0-9]'
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
is_excluded() {
|
|
99
|
+
local rel="$1" seg leaf g
|
|
100
|
+
local -a segs
|
|
101
|
+
IFS='/' read -ra segs <<< "$rel"
|
|
102
|
+
for seg in "${segs[@]}"; do
|
|
103
|
+
for g in "${EXCLUDE_DIRS[@]}"; do
|
|
104
|
+
[ "$seg" = "$g" ] && return 0
|
|
105
|
+
done
|
|
106
|
+
done
|
|
107
|
+
leaf="${rel##*/}"
|
|
108
|
+
for g in "${EXCLUDE_FILES[@]}"; do
|
|
109
|
+
# shellcheck disable=SC2254
|
|
110
|
+
case "$leaf" in $g) return 0 ;; esac
|
|
111
|
+
done
|
|
112
|
+
return 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
is_binary() {
|
|
116
|
+
local f="$1" n1 n2
|
|
117
|
+
n1="$(head -c 8000 "$f" | wc -c | tr -d '[:space:]')"
|
|
118
|
+
n2="$(head -c 8000 "$f" | LC_ALL=C tr -d '\000' | wc -c | tr -d '[:space:]')"
|
|
119
|
+
[ "$n1" != "$n2" ]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# ----------------------------------------------------------- collect + sort ---
|
|
123
|
+
picked=()
|
|
124
|
+
while IFS= read -r full; do
|
|
125
|
+
rel="${full#"$SRC"/}"
|
|
126
|
+
is_excluded "$rel" && continue
|
|
127
|
+
picked+=("$rel")
|
|
128
|
+
done < <(find "$SRC" -type f | LC_ALL=C sort)
|
|
129
|
+
|
|
130
|
+
[ "${#picked[@]}" -gt 0 ] || { echo "Error: No files to pack under: $SRC" >&2; exit 1; }
|
|
131
|
+
|
|
132
|
+
# ----------------------------------------------------------- secret scan -----
|
|
133
|
+
findings=()
|
|
134
|
+
for rel in "${picked[@]}"; do
|
|
135
|
+
f="$SRC/$rel"
|
|
136
|
+
for i in "${!SECRET_RX[@]}"; do
|
|
137
|
+
if LC_ALL=C grep -aEq -- "${SECRET_RX[$i]}" "$f" 2>/dev/null; then
|
|
138
|
+
findings+=(" $rel -> ${SECRET_NAMES[$i]}")
|
|
139
|
+
fi
|
|
140
|
+
done
|
|
141
|
+
done
|
|
142
|
+
if [ "${#findings[@]}" -gt 0 ]; then
|
|
143
|
+
if [ "$ALLOW_SECRETS" -eq 1 ]; then
|
|
144
|
+
echo "WARNING: Potential secret(s) detected — packing anyway (--allow-secrets):" >&2
|
|
145
|
+
printf '%s\n' "${findings[@]}" >&2
|
|
146
|
+
else
|
|
147
|
+
echo "Error: Potential secret(s) detected — refusing to pack:" >&2
|
|
148
|
+
printf '%s\n' "${findings[@]}" >&2
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# ----------------------------------------------------------- sentinel check --
|
|
154
|
+
hits=0
|
|
155
|
+
for rel in "${picked[@]}"; do
|
|
156
|
+
if LC_ALL=C grep -aFq -- "$SENTINEL" "$SRC/$rel" 2>/dev/null; then
|
|
157
|
+
hits=$((hits + 1))
|
|
158
|
+
fi
|
|
159
|
+
done
|
|
160
|
+
if [ "$hits" -eq 0 ]; then
|
|
161
|
+
echo "WARNING: Sentinel '$SENTINEL' not found in any file. Did you author the sample under that exact name?" >&2
|
|
162
|
+
else
|
|
163
|
+
echo "Sentinel '$SENTINEL' found in $hits file(s)."
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# ----------------------------------------------------------- write the pack --
|
|
167
|
+
outdir="$(dirname "$OUT_FILE")"
|
|
168
|
+
[ -d "$outdir" ] || mkdir -p "$outdir"
|
|
169
|
+
|
|
170
|
+
{
|
|
171
|
+
printf '%s\n' '# AW-SPEC REFERENCE PACK'
|
|
172
|
+
printf '# foundation: %s\n' "$FOUNDATION"
|
|
173
|
+
printf '# sentinel: %s\n' "$SENTINEL"
|
|
174
|
+
printf '# generated: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%S.0000000Z)"
|
|
175
|
+
printf '# files: %s\n' "${#picked[@]}"
|
|
176
|
+
printf '%s\n' '# format: aw-spec-pack/v1'
|
|
177
|
+
printf '%s\n' '# --- replacement contract (ordinal, case-sensitive, longest-first) ---'
|
|
178
|
+
printf '%s\n' '# <Sentinel> -> <AppName> (dotted: package.json name as dotted)'
|
|
179
|
+
printf '%s\n' '# <Sentinel _-form> -> <AppName _-form> (underscored: identifiers)'
|
|
180
|
+
printf '%s\n' '# <sentinel --form> -> <appname --form> (lower-hyphen: npm package name, docker image)'
|
|
181
|
+
printf '%s\n' '# <sentinel .-form> -> <appname lower-dot> (lower-dot: lowercased identifiers)'
|
|
182
|
+
printf '%s\n' '# Applied to file CONTENTS and file/dir PATHS. Never touch other AppWeaver.* tokens.'
|
|
183
|
+
printf '%s\n' '# ============================================================'
|
|
184
|
+
printf '\n'
|
|
185
|
+
} > "$OUT_FILE"
|
|
186
|
+
|
|
187
|
+
for rel in "${picked[@]}"; do
|
|
188
|
+
f="$SRC/$rel"
|
|
189
|
+
if is_binary "$f"; then
|
|
190
|
+
{
|
|
191
|
+
printf '<<<FILE path="%s" encoding="base64">>>\n' "$rel"
|
|
192
|
+
base64 < "$f" | tr -d '\n'
|
|
193
|
+
printf '\n'
|
|
194
|
+
printf '%s\n' '<<<ENDFILE>>>'
|
|
195
|
+
} >> "$OUT_FILE"
|
|
196
|
+
else
|
|
197
|
+
{
|
|
198
|
+
printf '<<<FILE path="%s">>>\n' "$rel"
|
|
199
|
+
awk '{ sub(/\r$/, ""); print }' "$f"
|
|
200
|
+
printf '%s\n' '<<<ENDFILE>>>'
|
|
201
|
+
} >> "$OUT_FILE"
|
|
202
|
+
fi
|
|
203
|
+
done
|
|
204
|
+
|
|
205
|
+
echo "Packed ${#picked[@]} file(s) -> $OUT_FILE"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Unpack an AW-Spec foundation pack file into a target application, renaming the
|
|
5
|
+
sentinel app name to the caller's app name.
|
|
6
|
+
|
|
7
|
+
.DESCRIPTION
|
|
8
|
+
Reads -PackFile, takes the sentinel from its header (override with -Sentinel),
|
|
9
|
+
derives four case forms of the target app name, and writes every file block to
|
|
10
|
+
-OutDir with the sentinel rewritten in both file CONTENTS and file/dir PATHS.
|
|
11
|
+
Text blocks are rewritten and written as UTF-8; base64 blocks are decoded as-is.
|
|
12
|
+
Refuses to overwrite existing files unless -Force.
|
|
13
|
+
|
|
14
|
+
.EXAMPLE
|
|
15
|
+
./unpack-foundation.ps1 -PackFile ./UI.Visualization.pack.txt -TargetAppName MyApp -OutDir ../../
|
|
16
|
+
#>
|
|
17
|
+
[CmdletBinding()]
|
|
18
|
+
param(
|
|
19
|
+
[Parameter(Mandatory)] [string] $PackFile,
|
|
20
|
+
[Parameter(Mandatory)] [string] $TargetAppName,
|
|
21
|
+
[string] $OutDir = '.',
|
|
22
|
+
[string] $Sentinel,
|
|
23
|
+
[switch] $Force,
|
|
24
|
+
[switch] $DryRun
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
$ErrorActionPreference = 'Stop'
|
|
28
|
+
|
|
29
|
+
$pack = (Resolve-Path -LiteralPath $PackFile).Path
|
|
30
|
+
$lines = ([System.IO.File]::ReadAllText($pack)) -split "`r?`n"
|
|
31
|
+
|
|
32
|
+
# --- sentinel from header unless overridden ---
|
|
33
|
+
if (-not $Sentinel) {
|
|
34
|
+
foreach ($l in $lines) {
|
|
35
|
+
if ($l -match '^#\s*sentinel:\s*(.+?)\s*$') { $Sentinel = $Matches[1]; break }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (-not $Sentinel) { throw "Sentinel not found in pack header; supply it with -Sentinel." }
|
|
39
|
+
|
|
40
|
+
# --- derive case forms ---
|
|
41
|
+
function Conv-Underscore ([string] $s) { $s -replace '\.','_' }
|
|
42
|
+
function Conv-LowerHyphen ([string] $s) { ($s.ToLowerInvariant()) -replace '\.','-' }
|
|
43
|
+
function Conv-LowerDot ([string] $s) { $s.ToLowerInvariant() }
|
|
44
|
+
|
|
45
|
+
# NOTE: do NOT use a hashtable here — PowerShell hashtable keys are case-INSENSITIVE,
|
|
46
|
+
# which would collapse the dotted ("AppWeaver.Foundation") and lower-dot
|
|
47
|
+
# ("appweaver.foundation") forms into one entry and drop the namespace rename.
|
|
48
|
+
$forms = @(
|
|
49
|
+
@{ From = $Sentinel; To = $TargetAppName }
|
|
50
|
+
@{ From = (Conv-Underscore $Sentinel); To = (Conv-Underscore $TargetAppName) }
|
|
51
|
+
@{ From = (Conv-LowerHyphen $Sentinel); To = (Conv-LowerHyphen $TargetAppName) }
|
|
52
|
+
@{ From = (Conv-LowerDot $Sentinel); To = (Conv-LowerDot $TargetAppName) }
|
|
53
|
+
)
|
|
54
|
+
$replacements = @()
|
|
55
|
+
$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
|
|
56
|
+
foreach ($pair in $forms) {
|
|
57
|
+
$from = [string]$pair.From
|
|
58
|
+
$to = [string]$pair.To
|
|
59
|
+
if ([string]::IsNullOrEmpty($from)) { continue }
|
|
60
|
+
if ($from -ceq $to) { continue } # -ceq: case-sensitive no-op check
|
|
61
|
+
if (-not $seen.Add($from)) { continue } # ordinal dedupe of identical From tokens
|
|
62
|
+
$replacements += [pscustomobject]@{ From = $from; To = $to }
|
|
63
|
+
}
|
|
64
|
+
# longest token first so overlaps resolve correctly
|
|
65
|
+
$replacements = @($replacements | Sort-Object { $_.From.Length } -Descending)
|
|
66
|
+
|
|
67
|
+
function Apply-Repl([string] $text) {
|
|
68
|
+
foreach ($r in $replacements) { $text = $text.Replace($r.From, $r.To) }
|
|
69
|
+
return $text
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# --- output root ---
|
|
73
|
+
if (-not (Test-Path -LiteralPath $OutDir)) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null }
|
|
74
|
+
$outRoot = (Resolve-Path -LiteralPath $OutDir).Path
|
|
75
|
+
$sep = [System.IO.Path]::DirectorySeparatorChar
|
|
76
|
+
|
|
77
|
+
# --- parse file blocks ---
|
|
78
|
+
$files = @()
|
|
79
|
+
$i = 0
|
|
80
|
+
while ($i -lt $lines.Count) {
|
|
81
|
+
if ($lines[$i] -match '^<<<FILE path="(.+?)"(?:\s+encoding="(\w+)")?>>>\s*$') {
|
|
82
|
+
$rel = $Matches[1]; $enc = $Matches[2]
|
|
83
|
+
$i++
|
|
84
|
+
$buf = New-Object System.Collections.Generic.List[string]
|
|
85
|
+
while ($i -lt $lines.Count -and $lines[$i] -ne '<<<ENDFILE>>>') {
|
|
86
|
+
$buf.Add($lines[$i]); $i++
|
|
87
|
+
}
|
|
88
|
+
$files += [pscustomobject]@{ Rel = $rel; Enc = $enc; Content = ($buf -join "`n") }
|
|
89
|
+
}
|
|
90
|
+
$i++
|
|
91
|
+
}
|
|
92
|
+
if ($files.Count -eq 0) { throw "No <<<FILE>>> blocks found in pack: $pack" }
|
|
93
|
+
|
|
94
|
+
function Resolve-Dest([string] $rel) {
|
|
95
|
+
$renamed = (Apply-Repl $rel).Replace('/', $sep)
|
|
96
|
+
return (Join-Path $outRoot $renamed)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# --- conflict guard ---
|
|
100
|
+
$conflicts = foreach ($f in $files) { $d = Resolve-Dest $f.Rel; if (Test-Path -LiteralPath $d) { $d } }
|
|
101
|
+
if ($conflicts -and -not $Force) {
|
|
102
|
+
throw ("Refusing to overwrite existing files (re-run with -Force):`n " + ($conflicts -join "`n "))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# --- write ---
|
|
106
|
+
$written = 0
|
|
107
|
+
foreach ($f in $files) {
|
|
108
|
+
$dest = Resolve-Dest $f.Rel
|
|
109
|
+
$destDir = Split-Path -Parent $dest
|
|
110
|
+
if ($DryRun) { Write-Host "WOULD WRITE $($f.Rel) -> $dest"; continue }
|
|
111
|
+
if ($destDir -and -not (Test-Path -LiteralPath $destDir)) {
|
|
112
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
113
|
+
}
|
|
114
|
+
if ($f.Enc -eq 'base64') {
|
|
115
|
+
[System.IO.File]::WriteAllBytes($dest, [Convert]::FromBase64String(($f.Content -replace '\s','')))
|
|
116
|
+
} else {
|
|
117
|
+
[System.IO.File]::WriteAllText($dest, (Apply-Repl $f.Content), [System.Text.UTF8Encoding]::new($false))
|
|
118
|
+
}
|
|
119
|
+
$written++
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ($DryRun) {
|
|
123
|
+
Write-Host "DryRun: $($files.Count) file(s) would be written to $outRoot (sentinel '$Sentinel' -> '$TargetAppName')."
|
|
124
|
+
} else {
|
|
125
|
+
Write-Host "Unpacked $written file(s) to $outRoot (sentinel '$Sentinel' -> '$TargetAppName')."
|
|
126
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# unpack-foundation.sh — bash/macOS+Unix port of unpack-foundation.ps1
|
|
4
|
+
#
|
|
5
|
+
# SYNOPSIS
|
|
6
|
+
# Unpack an AW-Spec foundation pack file into a target application, renaming the
|
|
7
|
+
# sentinel app name to the caller's app name.
|
|
8
|
+
#
|
|
9
|
+
# DESCRIPTION
|
|
10
|
+
# Reads --pack-file, takes the sentinel from its header (override with --sentinel),
|
|
11
|
+
# derives four case forms of the target app name, and writes every file block to
|
|
12
|
+
# --out-dir with the sentinel rewritten in both file CONTENTS and file/dir PATHS.
|
|
13
|
+
# Text blocks are rewritten and written as UTF-8; base64 blocks are decoded as-is.
|
|
14
|
+
# Refuses to overwrite existing files unless --force. Reads packs produced by either
|
|
15
|
+
# pack-foundation.sh or pack-foundation.ps1.
|
|
16
|
+
#
|
|
17
|
+
# EXAMPLE
|
|
18
|
+
# ./unpack-foundation.sh --pack-file ./UI.Visualization.pack.txt --target-app-name MyApp --out-dir ../../
|
|
19
|
+
#
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------ defaults ---
|
|
23
|
+
PACK_FILE=''
|
|
24
|
+
TARGET_APP_NAME=''
|
|
25
|
+
OUT_DIR='.'
|
|
26
|
+
SENTINEL=''
|
|
27
|
+
FORCE=0
|
|
28
|
+
DRYRUN=0
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat >&2 <<'EOF'
|
|
32
|
+
Usage: unpack-foundation.sh --pack-file <file> --target-app-name <name>
|
|
33
|
+
[--out-dir <dir>] [--sentinel <name>] [--force] [--dry-run]
|
|
34
|
+
|
|
35
|
+
-p, --pack-file <file> Pack file to unpack (required)
|
|
36
|
+
-t, --target-app-name <name> New app name to rename the sentinel into (required)
|
|
37
|
+
-o, --out-dir <dir> Output root (default: . / repo root by skill)
|
|
38
|
+
--sentinel <name> Override the sentinel from the pack header
|
|
39
|
+
--force Overwrite files that already exist
|
|
40
|
+
--dry-run List what would be written, write nothing
|
|
41
|
+
-h, --help Show this help
|
|
42
|
+
|
|
43
|
+
Positional fallback: unpack-foundation.sh <pack-file> <target-app-name> [out-dir]
|
|
44
|
+
EOF
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# -------------------------------------------------------------- arg parsing ---
|
|
48
|
+
while [ $# -gt 0 ]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
-p|--pack-file) PACK_FILE="${2:?--pack-file needs a value}"; shift 2 ;;
|
|
51
|
+
-t|--target-app-name) TARGET_APP_NAME="${2:?--target-app-name needs a value}"; shift 2 ;;
|
|
52
|
+
-o|--out-dir) OUT_DIR="${2:?--out-dir needs a value}"; shift 2 ;;
|
|
53
|
+
--sentinel) SENTINEL="${2:?--sentinel needs a value}"; shift 2 ;;
|
|
54
|
+
--force) FORCE=1; shift ;;
|
|
55
|
+
--dry-run) DRYRUN=1; shift ;;
|
|
56
|
+
-h|--help) usage; exit 0 ;;
|
|
57
|
+
--) shift; break ;;
|
|
58
|
+
-*) echo "Error: unknown option: $1" >&2; usage; exit 2 ;;
|
|
59
|
+
*)
|
|
60
|
+
if [ -z "$PACK_FILE" ]; then PACK_FILE="$1"
|
|
61
|
+
elif [ -z "$TARGET_APP_NAME" ]; then TARGET_APP_NAME="$1"
|
|
62
|
+
else OUT_DIR="$1"
|
|
63
|
+
fi
|
|
64
|
+
shift ;;
|
|
65
|
+
esac
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
[ -n "$PACK_FILE" ] || { echo "Error: --pack-file is required." >&2; usage; exit 2; }
|
|
69
|
+
[ -n "$TARGET_APP_NAME" ] || { echo "Error: --target-app-name is required." >&2; usage; exit 2; }
|
|
70
|
+
[ -f "$PACK_FILE" ] || { echo "Error: pack file not found: $PACK_FILE" >&2; exit 1; }
|
|
71
|
+
|
|
72
|
+
# Keep replacement tokens safe for bash literal substitution / path joins.
|
|
73
|
+
case "$TARGET_APP_NAME" in
|
|
74
|
+
*[!A-Za-z0-9._-]*)
|
|
75
|
+
echo "Error: --target-app-name may contain only letters, digits, '.', '_' or '-': $TARGET_APP_NAME" >&2
|
|
76
|
+
exit 2 ;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
79
|
+
# ------------------------------------------------ sentinel from header --------
|
|
80
|
+
if [ -z "$SENTINEL" ]; then
|
|
81
|
+
SENTINEL="$(LC_ALL=C sed -n 's/^#[[:space:]]*sentinel:[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$/\1/p' "$PACK_FILE" | head -n1)"
|
|
82
|
+
fi
|
|
83
|
+
[ -n "$SENTINEL" ] || { echo "Error: Sentinel not found in pack header; supply it with --sentinel." >&2; exit 1; }
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------ derive case forms -----------
|
|
86
|
+
to_lower() { printf '%s' "$1" | LC_ALL=C tr '[:upper:]' '[:lower:]'; }
|
|
87
|
+
|
|
88
|
+
S_DOT="$SENTINEL"; T_DOT="$TARGET_APP_NAME" # dotted
|
|
89
|
+
S_UND="${SENTINEL//./_}"; T_UND="${TARGET_APP_NAME//./_}" # underscored
|
|
90
|
+
s_low="$(to_lower "$SENTINEL")"; t_low="$(to_lower "$TARGET_APP_NAME")"
|
|
91
|
+
S_HYP="${s_low//./-}"; T_HYP="${t_low//./-}" # lower-hyphen
|
|
92
|
+
S_LDOT="$s_low"; T_LDOT="$t_low" # lower-dot
|
|
93
|
+
|
|
94
|
+
# Build replacement list: dedupe by From (ordinal), drop no-ops, sort longest-first.
|
|
95
|
+
FROM=(); TO=()
|
|
96
|
+
add_form() {
|
|
97
|
+
local from="$1" to="$2" e
|
|
98
|
+
[ -n "$from" ] || return 0
|
|
99
|
+
[ "$from" = "$to" ] && return 0
|
|
100
|
+
if [ "${#FROM[@]}" -gt 0 ]; then
|
|
101
|
+
for e in "${FROM[@]}"; do [ "$e" = "$from" ] && return 0; done
|
|
102
|
+
fi
|
|
103
|
+
FROM+=("$from"); TO+=("$to")
|
|
104
|
+
}
|
|
105
|
+
add_form "$S_DOT" "$T_DOT"
|
|
106
|
+
add_form "$S_UND" "$T_UND"
|
|
107
|
+
add_form "$S_HYP" "$T_HYP"
|
|
108
|
+
add_form "$S_LDOT" "$T_LDOT"
|
|
109
|
+
|
|
110
|
+
# Order indices by FROM length descending (stable for equal lengths).
|
|
111
|
+
ORDER=()
|
|
112
|
+
{
|
|
113
|
+
local_n="${#FROM[@]}"
|
|
114
|
+
used=(); for ((k = 0; k < local_n; k++)); do used[$k]=0; done
|
|
115
|
+
for ((pass = 0; pass < local_n; pass++)); do
|
|
116
|
+
best=-1; bestlen=-1
|
|
117
|
+
for ((j = 0; j < local_n; j++)); do
|
|
118
|
+
if [ "${used[$j]}" -eq 0 ] && [ "${#FROM[$j]}" -gt "$bestlen" ]; then
|
|
119
|
+
bestlen="${#FROM[$j]}"; best="$j"
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
used[$best]=1; ORDER+=("$best")
|
|
123
|
+
done
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
REPL_OUT=''
|
|
127
|
+
apply_repl() { # literal, ordinal, longest-first; result in $REPL_OUT (no subshell trim)
|
|
128
|
+
local text="$1" idx from to
|
|
129
|
+
if [ "${#ORDER[@]}" -gt 0 ]; then # empty when target == sentinel (no-op rename)
|
|
130
|
+
for idx in "${ORDER[@]}"; do
|
|
131
|
+
from="${FROM[$idx]}"; to="${TO[$idx]}"
|
|
132
|
+
text="${text//"$from"/$to}" # quoted pattern => literal (non-glob) match
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
REPL_OUT="$text"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------ output root ----------------
|
|
139
|
+
[ -d "$OUT_DIR" ] || mkdir -p "$OUT_DIR"
|
|
140
|
+
OUT_ROOT="$(cd "$OUT_DIR" && pwd -P)"
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------ base64 decoder (portable) --
|
|
143
|
+
B64D=()
|
|
144
|
+
if printf 'aGk=' | base64 -d >/dev/null 2>&1; then B64D=(base64 -d)
|
|
145
|
+
elif printf 'aGk=' | base64 -D >/dev/null 2>&1; then B64D=(base64 -D)
|
|
146
|
+
elif printf 'aGk=' | base64 --decode >/dev/null 2>&1; then B64D=(base64 --decode)
|
|
147
|
+
else B64D=(openssl base64 -d -A)
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# ------------------------------------------------ parse file blocks ----------
|
|
151
|
+
rels=(); encs=(); contents=()
|
|
152
|
+
in_block=0; cur_rel=''; cur_enc=''; cur_content=''; first_line=1
|
|
153
|
+
file_re='^<<<FILE path="([^"]+)"( encoding="([^"]+)")?>>>[[:space:]]*$'
|
|
154
|
+
|
|
155
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
156
|
+
line="${line%$'\r'}" # tolerate CRLF packs (e.g. git autocrlf on Windows checkout)
|
|
157
|
+
if [ "$in_block" -eq 0 ]; then
|
|
158
|
+
if [[ "$line" =~ $file_re ]]; then
|
|
159
|
+
cur_rel="${BASH_REMATCH[1]}"
|
|
160
|
+
cur_enc="${BASH_REMATCH[3]:-}"
|
|
161
|
+
in_block=1; first_line=1; cur_content=''
|
|
162
|
+
fi
|
|
163
|
+
else
|
|
164
|
+
if [ "$line" = '<<<ENDFILE>>>' ]; then
|
|
165
|
+
rels+=("$cur_rel"); encs+=("$cur_enc"); contents+=("$cur_content")
|
|
166
|
+
in_block=0
|
|
167
|
+
elif [ "$first_line" -eq 1 ]; then
|
|
168
|
+
cur_content="$line"; first_line=0
|
|
169
|
+
else
|
|
170
|
+
cur_content="$cur_content"$'\n'"$line"
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
done < "$PACK_FILE"
|
|
174
|
+
|
|
175
|
+
[ "${#rels[@]}" -gt 0 ] || { echo "Error: No <<<FILE>>> blocks found in pack: $PACK_FILE" >&2; exit 1; }
|
|
176
|
+
|
|
177
|
+
# ------------------------------------------------ resolve destinations -------
|
|
178
|
+
dests=()
|
|
179
|
+
for ((i = 0; i < ${#rels[@]}; i++)); do
|
|
180
|
+
apply_repl "${rels[$i]}"
|
|
181
|
+
dests+=("$OUT_ROOT/$REPL_OUT")
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
# ------------------------------------------------ conflict guard -------------
|
|
185
|
+
if [ "$FORCE" -eq 0 ] && [ "$DRYRUN" -eq 0 ]; then
|
|
186
|
+
conflicts=()
|
|
187
|
+
for d in "${dests[@]}"; do [ -e "$d" ] && conflicts+=("$d"); done
|
|
188
|
+
if [ "${#conflicts[@]}" -gt 0 ]; then
|
|
189
|
+
echo "Error: Refusing to overwrite existing files (re-run with --force):" >&2
|
|
190
|
+
printf ' %s\n' "${conflicts[@]}" >&2
|
|
191
|
+
exit 1
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# ------------------------------------------------ write ----------------------
|
|
196
|
+
written=0
|
|
197
|
+
for ((i = 0; i < ${#rels[@]}; i++)); do
|
|
198
|
+
rel="${rels[$i]}"; enc="${encs[$i]}"; dest="${dests[$i]}"
|
|
199
|
+
if [ "$DRYRUN" -eq 1 ]; then
|
|
200
|
+
echo "WOULD WRITE $rel -> $dest"
|
|
201
|
+
continue
|
|
202
|
+
fi
|
|
203
|
+
mkdir -p "$(dirname "$dest")"
|
|
204
|
+
if [ "$enc" = 'base64' ]; then
|
|
205
|
+
printf '%s' "${contents[$i]}" | tr -d '[:space:]' | "${B64D[@]}" > "$dest"
|
|
206
|
+
else
|
|
207
|
+
apply_repl "${contents[$i]}"
|
|
208
|
+
printf '%s' "$REPL_OUT" > "$dest"
|
|
209
|
+
fi
|
|
210
|
+
written=$((written + 1))
|
|
211
|
+
done
|
|
212
|
+
|
|
213
|
+
if [ "$DRYRUN" -eq 1 ]; then
|
|
214
|
+
echo "DryRun: ${#rels[@]} file(s) would be written to $OUT_ROOT (sentinel '$SENTINEL' -> '$TARGET_APP_NAME')."
|
|
215
|
+
else
|
|
216
|
+
echo "Unpacked $written file(s) to $OUT_ROOT (sentinel '$SENTINEL' -> '$TARGET_APP_NAME')."
|
|
217
|
+
fi
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Unpack an AW-Spec reference pack file into a target project, renaming the
|
|
5
|
+
sentinel app name to the caller's app name.
|
|
6
|
+
|
|
7
|
+
.DESCRIPTION
|
|
8
|
+
Reads -PackFile (produced by pack-reference-foundation.ps1), takes the sentinel from
|
|
9
|
+
its header (override with -Sentinel), derives four case forms of the target app name,
|
|
10
|
+
and writes every file block to -OutDir with the sentinel rewritten in both file
|
|
11
|
+
CONTENTS and file/dir PATHS. Text blocks are rewritten and written as UTF-8;
|
|
12
|
+
base64 blocks are decoded as-is. Refuses to overwrite existing files unless -Force.
|
|
13
|
+
|
|
14
|
+
.EXAMPLE
|
|
15
|
+
./unpack-reference-foundation.ps1 `
|
|
16
|
+
-PackFile ./UI.Visualization.Reference.pack.txt `
|
|
17
|
+
-TargetAppName MyApp `
|
|
18
|
+
-OutDir ../../
|
|
19
|
+
#>
|
|
20
|
+
[CmdletBinding()]
|
|
21
|
+
param(
|
|
22
|
+
[Parameter(Mandatory)] [string] $PackFile,
|
|
23
|
+
[Parameter(Mandatory)] [string] $TargetAppName,
|
|
24
|
+
[string] $OutDir = '.',
|
|
25
|
+
[string] $Sentinel,
|
|
26
|
+
[switch] $Force,
|
|
27
|
+
[switch] $DryRun
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
$ErrorActionPreference = 'Stop'
|
|
31
|
+
|
|
32
|
+
$pack = (Resolve-Path -LiteralPath $PackFile).Path
|
|
33
|
+
$lines = ([System.IO.File]::ReadAllText($pack)) -split "`r?`n"
|
|
34
|
+
|
|
35
|
+
# --- sentinel from header unless overridden ---
|
|
36
|
+
if (-not $Sentinel) {
|
|
37
|
+
foreach ($l in $lines) {
|
|
38
|
+
if ($l -match '^#\s*sentinel:\s*(.+?)\s*$') { $Sentinel = $Matches[1]; break }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (-not $Sentinel) { throw "Sentinel not found in pack header; supply it with -Sentinel." }
|
|
42
|
+
|
|
43
|
+
# --- derive case forms ---
|
|
44
|
+
function Conv-Underscore ([string] $s) { $s -replace '\.','_' }
|
|
45
|
+
function Conv-LowerHyphen ([string] $s) { ($s.ToLowerInvariant()) -replace '\.','-' }
|
|
46
|
+
function Conv-LowerDot ([string] $s) { $s.ToLowerInvariant() }
|
|
47
|
+
|
|
48
|
+
# NOTE: do NOT use a hashtable here — PowerShell hashtable keys are case-INSENSITIVE,
|
|
49
|
+
# which would collapse the dotted ("AppWeaver.VerticalSlice") and lower-dot
|
|
50
|
+
# ("appweaver.verticalslice") forms into one entry and drop the namespace rename.
|
|
51
|
+
$forms = @(
|
|
52
|
+
@{ From = $Sentinel; To = $TargetAppName }
|
|
53
|
+
@{ From = (Conv-Underscore $Sentinel); To = (Conv-Underscore $TargetAppName) }
|
|
54
|
+
@{ From = (Conv-LowerHyphen $Sentinel); To = (Conv-LowerHyphen $TargetAppName) }
|
|
55
|
+
@{ From = (Conv-LowerDot $Sentinel); To = (Conv-LowerDot $TargetAppName) }
|
|
56
|
+
)
|
|
57
|
+
$replacements = @()
|
|
58
|
+
$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
|
|
59
|
+
foreach ($pair in $forms) {
|
|
60
|
+
$from = [string]$pair.From
|
|
61
|
+
$to = [string]$pair.To
|
|
62
|
+
if ([string]::IsNullOrEmpty($from)) { continue }
|
|
63
|
+
if ($from -ceq $to) { continue }
|
|
64
|
+
if (-not $seen.Add($from)) { continue }
|
|
65
|
+
$replacements += [pscustomobject]@{ From = $from; To = $to }
|
|
66
|
+
}
|
|
67
|
+
# longest token first so overlaps resolve correctly
|
|
68
|
+
$replacements = @($replacements | Sort-Object { $_.From.Length } -Descending)
|
|
69
|
+
|
|
70
|
+
function Apply-Repl([string] $text) {
|
|
71
|
+
foreach ($r in $replacements) { $text = $text.Replace($r.From, $r.To) }
|
|
72
|
+
return $text
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# --- output root ---
|
|
76
|
+
if (-not (Test-Path -LiteralPath $OutDir)) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null }
|
|
77
|
+
$outRoot = (Resolve-Path -LiteralPath $OutDir).Path
|
|
78
|
+
$sep = [System.IO.Path]::DirectorySeparatorChar
|
|
79
|
+
|
|
80
|
+
# --- parse file blocks ---
|
|
81
|
+
$files = @()
|
|
82
|
+
$i = 0
|
|
83
|
+
while ($i -lt $lines.Count) {
|
|
84
|
+
if ($lines[$i] -match '^<<<FILE path="(.+?)"(?:\s+encoding="(\w+)")?>>>\s*$') {
|
|
85
|
+
$rel = $Matches[1]; $enc = $Matches[2]
|
|
86
|
+
$i++
|
|
87
|
+
$buf = New-Object System.Collections.Generic.List[string]
|
|
88
|
+
while ($i -lt $lines.Count -and $lines[$i] -ne '<<<ENDFILE>>>') {
|
|
89
|
+
$buf.Add($lines[$i]); $i++
|
|
90
|
+
}
|
|
91
|
+
$files += [pscustomobject]@{ Rel = $rel; Enc = $enc; Content = ($buf -join "`n") }
|
|
92
|
+
}
|
|
93
|
+
$i++
|
|
94
|
+
}
|
|
95
|
+
if ($files.Count -eq 0) { throw "No <<<FILE>>> blocks found in pack: $pack" }
|
|
96
|
+
|
|
97
|
+
function Resolve-Dest([string] $rel) {
|
|
98
|
+
$renamed = (Apply-Repl $rel).Replace('/', $sep)
|
|
99
|
+
return (Join-Path $outRoot $renamed)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# --- conflict guard ---
|
|
103
|
+
$conflicts = foreach ($f in $files) { $d = Resolve-Dest $f.Rel; if (Test-Path -LiteralPath $d) { $d } }
|
|
104
|
+
if ($conflicts -and -not $Force) {
|
|
105
|
+
throw ("Refusing to overwrite existing files (re-run with -Force):`n " + ($conflicts -join "`n "))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# --- write ---
|
|
109
|
+
$written = 0
|
|
110
|
+
foreach ($f in $files) {
|
|
111
|
+
$dest = Resolve-Dest $f.Rel
|
|
112
|
+
$destDir = Split-Path -Parent $dest
|
|
113
|
+
if ($DryRun) { Write-Host "WOULD WRITE $($f.Rel) -> $dest"; continue }
|
|
114
|
+
if ($destDir -and -not (Test-Path -LiteralPath $destDir)) {
|
|
115
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
116
|
+
}
|
|
117
|
+
if ($f.Enc -eq 'base64') {
|
|
118
|
+
[System.IO.File]::WriteAllBytes($dest, [Convert]::FromBase64String(($f.Content -replace '\s','')))
|
|
119
|
+
} else {
|
|
120
|
+
[System.IO.File]::WriteAllText($dest, (Apply-Repl $f.Content), [System.Text.UTF8Encoding]::new($false))
|
|
121
|
+
}
|
|
122
|
+
$written++
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if ($DryRun) {
|
|
126
|
+
Write-Host "DryRun: $($files.Count) file(s) would be written to $outRoot (sentinel '$Sentinel' -> '$TargetAppName')."
|
|
127
|
+
} else {
|
|
128
|
+
Write-Host "Unpacked $written file(s) to $outRoot (sentinel '$Sentinel' -> '$TargetAppName')."
|
|
129
|
+
}
|