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,165 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Pack the UI.Visualization foundation template into a single AW-Spec text pack file.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Walks -SourceDir (templates/AppWeaver.UI.Visualization/), excludes build/IDE artifacts,
|
|
8
|
+
JS/Next.js output folders, and secret-bearing files, and writes every remaining file into
|
|
9
|
+
one pack file using <<<FILE>>> / <<<ENDFILE>>> markers. Text files are stored verbatim
|
|
10
|
+
(UTF-8, LF); binary files are base64-encoded. The resulting pack file is consumed by
|
|
11
|
+
/aw-spec-foundation (see PACKAGING.md).
|
|
12
|
+
|
|
13
|
+
This is the UI counterpart of pack-foundation.ps1 in docs/VerticalSlice/.
|
|
14
|
+
|
|
15
|
+
.EXAMPLE
|
|
16
|
+
# From platform/AppWeaver.Spec/docs/UI.Visualization/
|
|
17
|
+
./pack-foundation.ps1 `
|
|
18
|
+
-SourceDir ../../../../templates/AppWeaver.UI.Visualization `
|
|
19
|
+
-OutFile ./UI.Visualization.pack.txt
|
|
20
|
+
|
|
21
|
+
.EXAMPLE
|
|
22
|
+
# Override sentinel and foundation name
|
|
23
|
+
./pack-foundation.ps1 `
|
|
24
|
+
-SourceDir ../../../../templates/AppWeaver.UI.Visualization `
|
|
25
|
+
-OutFile ./UI.Visualization.pack.txt `
|
|
26
|
+
-Sentinel UI.Visualization `
|
|
27
|
+
-Foundation UI.Visualization
|
|
28
|
+
#>
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param(
|
|
31
|
+
[Parameter(Mandatory)] [string] $SourceDir,
|
|
32
|
+
[string] $OutFile,
|
|
33
|
+
[string] $Sentinel = 'UI.Visualization',
|
|
34
|
+
[string] $Foundation = 'UI.Visualization',
|
|
35
|
+
[switch] $AllowSecrets
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
$ErrorActionPreference = 'Stop'
|
|
39
|
+
|
|
40
|
+
$src = (Resolve-Path -LiteralPath $SourceDir).Path
|
|
41
|
+
if (-not (Test-Path -LiteralPath $src -PathType Container)) {
|
|
42
|
+
throw "SourceDir is not a folder: $SourceDir"
|
|
43
|
+
}
|
|
44
|
+
if (-not $OutFile) { $OutFile = Join-Path (Get-Location).Path "$Foundation.pack.txt" }
|
|
45
|
+
|
|
46
|
+
# Entire subtree excluded if any path segment matches one of these.
|
|
47
|
+
# UI additions: .next, .turbo, dist, out, coverage, .cache
|
|
48
|
+
$excludeDirs = @(
|
|
49
|
+
'bin','obj','.vs','.git','.idea','.vscode','TestResults',
|
|
50
|
+
'node_modules','packages','App_Data','artifacts',
|
|
51
|
+
'.next','.turbo','dist','out','coverage','.cache','logs'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Files excluded by name glob (build noise, generated files, and secret-bearing files).
|
|
55
|
+
# NOTE: the root '.env' is intentionally NOT excluded — it carries required sample config.
|
|
56
|
+
# Per-environment overrides ('.env.local', '.env.production', ...) stay excluded via the glob.
|
|
57
|
+
$excludeFiles = @(
|
|
58
|
+
'*.user','*.suo','*.DotSettings.user','*.db','*.db-shm','*.db-wal','*.sqlite','*.log',
|
|
59
|
+
'.env.*','*.pfx','*.pem','*.key','*.snk','id_rsa','id_rsa.pub','secrets.json',
|
|
60
|
+
'.DS_Store', # macOS metadata
|
|
61
|
+
'*.tsbuildinfo', # TypeScript incremental build cache
|
|
62
|
+
'next-env.d.ts' # Next.js auto-generated — regenerated on first npm run dev
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Content patterns that indicate a real secret; packing aborts on a hit unless -AllowSecrets.
|
|
66
|
+
$secretPatterns = @(
|
|
67
|
+
@{ Name = 'NuGet ClearTextPassword'; Rx = 'ClearTextPassword' }
|
|
68
|
+
@{ Name = 'Private key block'; Rx = 'BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY' }
|
|
69
|
+
@{ Name = 'AWS access key id'; Rx = 'AKIA[0-9A-Z]{16}' }
|
|
70
|
+
@{ Name = 'Azure DevOps PAT-ish'; Rx = '[A-Za-z0-9]{52}AZDO[A-Za-z0-9]' }
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
function Test-Excluded([string] $relPath) {
|
|
74
|
+
foreach ($seg in ($relPath -split '[\\/]')) {
|
|
75
|
+
if ($excludeDirs -contains $seg) { return $true }
|
|
76
|
+
}
|
|
77
|
+
$leaf = Split-Path $relPath -Leaf
|
|
78
|
+
foreach ($g in $excludeFiles) { if ($leaf -like $g) { return $true } }
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Test-Binary([byte[]] $bytes) {
|
|
83
|
+
$n = [Math]::Min(8000, $bytes.Length)
|
|
84
|
+
for ($i = 0; $i -lt $n; $i++) { if ($bytes[$i] -eq 0) { return $true } }
|
|
85
|
+
return $false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Collect + filter
|
|
89
|
+
$picked = New-Object System.Collections.Generic.List[object]
|
|
90
|
+
foreach ($f in (Get-ChildItem -LiteralPath $src -Recurse -File -Force)) {
|
|
91
|
+
$rel = ($f.FullName.Substring($src.Length).TrimStart('\','/')) -replace '\\','/'
|
|
92
|
+
if (Test-Excluded $rel) { continue }
|
|
93
|
+
$picked.Add([pscustomobject]@{ Rel = $rel; Full = $f.FullName })
|
|
94
|
+
}
|
|
95
|
+
$picked = @($picked | Sort-Object Rel)
|
|
96
|
+
if ($picked.Count -eq 0) { throw "No files to pack under: $src" }
|
|
97
|
+
|
|
98
|
+
# Secret scan — fail closed so credentials never get baked into the committed bundle.
|
|
99
|
+
$findings = New-Object System.Collections.Generic.List[string]
|
|
100
|
+
foreach ($p in $picked) {
|
|
101
|
+
try { $t = [System.IO.File]::ReadAllText($p.Full) } catch { continue }
|
|
102
|
+
foreach ($pat in $secretPatterns) {
|
|
103
|
+
if ($t -match $pat.Rx) { $findings.Add(" $($p.Rel) -> $($pat.Name)") }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if ($findings.Count -gt 0) {
|
|
107
|
+
$msg = "Potential secret(s) detected — refusing to pack (rotate + remove them, or pass -AllowSecrets):`n" + ($findings -join "`n")
|
|
108
|
+
if ($AllowSecrets) { Write-Warning $msg } else { throw $msg }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Sentinel sanity check
|
|
112
|
+
$hits = 0
|
|
113
|
+
foreach ($p in $picked) {
|
|
114
|
+
try {
|
|
115
|
+
$t = [System.IO.File]::ReadAllText($p.Full)
|
|
116
|
+
if ($t.Contains($Sentinel)) { $hits++ }
|
|
117
|
+
} catch { }
|
|
118
|
+
}
|
|
119
|
+
if ($hits -eq 0) {
|
|
120
|
+
Write-Warning "Sentinel '$Sentinel' not found in any file. Did you author the template under that exact name?"
|
|
121
|
+
} else {
|
|
122
|
+
Write-Host "Sentinel '$Sentinel' found in $hits file(s)."
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Build the pack (explicit LF newlines throughout)
|
|
126
|
+
$sb = [System.Text.StringBuilder]::new()
|
|
127
|
+
function AddLine([string] $s) { [void]$sb.Append($s); [void]$sb.Append("`n") }
|
|
128
|
+
|
|
129
|
+
AddLine '# AW-SPEC FOUNDATION PACK'
|
|
130
|
+
AddLine "# foundation: $Foundation"
|
|
131
|
+
AddLine "# sentinel: $Sentinel"
|
|
132
|
+
AddLine "# generated: $([DateTime]::UtcNow.ToString('o'))"
|
|
133
|
+
AddLine "# files: $($picked.Count)"
|
|
134
|
+
AddLine '# format: aw-spec-pack/v1'
|
|
135
|
+
AddLine '# --- replacement contract (ordinal, case-sensitive, longest-first) ---'
|
|
136
|
+
AddLine '# <Sentinel> -> <AppName> (dotted: package.json name as dotted)'
|
|
137
|
+
AddLine '# <Sentinel _-form> -> <AppName _-form> (underscored: identifiers)'
|
|
138
|
+
AddLine '# <sentinel --form> -> <appname --form> (lower-hyphen: npm package name, docker image)'
|
|
139
|
+
AddLine '# <sentinel .-form> -> <appname lower-dot> (lower-dot: lowercased identifiers)'
|
|
140
|
+
AddLine '# Applied to file CONTENTS and file/dir PATHS. Never touch other AppWeaver.* tokens.'
|
|
141
|
+
AddLine '# ============================================================'
|
|
142
|
+
AddLine ''
|
|
143
|
+
|
|
144
|
+
foreach ($p in $picked) {
|
|
145
|
+
$bytes = [System.IO.File]::ReadAllBytes($p.Full)
|
|
146
|
+
if (Test-Binary $bytes) {
|
|
147
|
+
AddLine "<<<FILE path=`"$($p.Rel)`" encoding=`"base64`">>>"
|
|
148
|
+
AddLine ([Convert]::ToBase64String($bytes))
|
|
149
|
+
AddLine '<<<ENDFILE>>>'
|
|
150
|
+
} else {
|
|
151
|
+
$text = ([System.IO.File]::ReadAllText($p.Full)) -replace "`r`n", "`n"
|
|
152
|
+
AddLine "<<<FILE path=`"$($p.Rel)`">>>"
|
|
153
|
+
[void]$sb.Append($text)
|
|
154
|
+
if (-not $text.EndsWith("`n")) { [void]$sb.Append("`n") }
|
|
155
|
+
AddLine '<<<ENDFILE>>>'
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$outDir = Split-Path -Parent $OutFile
|
|
160
|
+
if ($outDir -and -not (Test-Path -LiteralPath $outDir)) {
|
|
161
|
+
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
|
162
|
+
}
|
|
163
|
+
[System.IO.File]::WriteAllText($OutFile, $sb.ToString(), [System.Text.UTF8Encoding]::new($false))
|
|
164
|
+
|
|
165
|
+
Write-Host "Packed $($picked.Count) file(s) -> $OutFile"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# pack-foundation.sh — bash/macOS port of pack-foundation.ps1
|
|
4
|
+
#
|
|
5
|
+
# SYNOPSIS
|
|
6
|
+
# Pack the UI.Visualization foundation template into a single AW-Spec text pack file.
|
|
7
|
+
#
|
|
8
|
+
# DESCRIPTION
|
|
9
|
+
# Walks --source-dir (templates/AppWeaver.UI.Visualization/), excludes build/IDE artifacts,
|
|
10
|
+
# JS/Next.js output folders, and secret-bearing files, and writes every remaining file into
|
|
11
|
+
# one pack file using <<<FILE>>> / <<<ENDFILE>>> markers. Text files are stored verbatim
|
|
12
|
+
# (UTF-8, LF); binary files are base64-encoded. Format-compatible with unpack-foundation.ps1.
|
|
13
|
+
#
|
|
14
|
+
# EXAMPLE
|
|
15
|
+
# # From platform/AppWeaver.Spec/docs/UI.Visualization/
|
|
16
|
+
# ./pack-foundation.sh \
|
|
17
|
+
# --source-dir ../../../../templates/AppWeaver.UI.Visualization \
|
|
18
|
+
# --out-file ./UI.Visualization.pack.txt
|
|
19
|
+
#
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------ defaults ---
|
|
23
|
+
SENTINEL='AppWeaver.UI.Visualization'
|
|
24
|
+
FOUNDATION='AppWeaver.UI.Visualization'
|
|
25
|
+
SOURCE_DIR=''
|
|
26
|
+
OUT_FILE=''
|
|
27
|
+
ALLOW_SECRETS=0
|
|
28
|
+
|
|
29
|
+
usage() {
|
|
30
|
+
cat >&2 <<'EOF'
|
|
31
|
+
Usage: pack-foundation.sh --source-dir <dir> [--out-file <file>] [--sentinel <name>]
|
|
32
|
+
[--foundation <name>] [--allow-secrets]
|
|
33
|
+
|
|
34
|
+
-s, --source-dir <dir> templates/AppWeaver.UI.Visualization folder (required)
|
|
35
|
+
-o, --out-file <file> Output pack file (default: ./<foundation>.pack.txt)
|
|
36
|
+
--sentinel <name> Sentinel app name (default: UI.Visualization)
|
|
37
|
+
--foundation <name> Foundation name (default: UI.Visualization)
|
|
38
|
+
--allow-secrets Pack even if credential markers are detected
|
|
39
|
+
-h, --help Show this help
|
|
40
|
+
|
|
41
|
+
Positional fallback: pack-foundation.sh <source-dir> [out-file]
|
|
42
|
+
EOF
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# -------------------------------------------------------------- arg parsing ---
|
|
46
|
+
while [ $# -gt 0 ]; do
|
|
47
|
+
case "$1" in
|
|
48
|
+
-s|--source-dir) SOURCE_DIR="${2:?--source-dir needs a value}"; shift 2 ;;
|
|
49
|
+
-o|--out-file) OUT_FILE="${2:?--out-file needs a value}"; shift 2 ;;
|
|
50
|
+
--sentinel) SENTINEL="${2:?--sentinel needs a value}"; shift 2 ;;
|
|
51
|
+
--foundation) FOUNDATION="${2:?--foundation needs a value}"; shift 2 ;;
|
|
52
|
+
--allow-secrets) ALLOW_SECRETS=1; shift ;;
|
|
53
|
+
-h|--help) usage; exit 0 ;;
|
|
54
|
+
--) shift; break ;;
|
|
55
|
+
-*) echo "Error: unknown option: $1" >&2; usage; exit 2 ;;
|
|
56
|
+
*)
|
|
57
|
+
if [ -z "$SOURCE_DIR" ]; then SOURCE_DIR="$1"
|
|
58
|
+
elif [ -z "$OUT_FILE" ]; then OUT_FILE="$1"
|
|
59
|
+
else echo "Error: unexpected argument: $1" >&2; usage; exit 2
|
|
60
|
+
fi
|
|
61
|
+
shift ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
[ -n "$SOURCE_DIR" ] || { echo "Error: --source-dir is required." >&2; usage; exit 2; }
|
|
66
|
+
[ -d "$SOURCE_DIR" ] || { echo "Error: SourceDir is not a folder: $SOURCE_DIR" >&2; exit 1; }
|
|
67
|
+
|
|
68
|
+
SRC="$(cd "$SOURCE_DIR" && pwd -P)"
|
|
69
|
+
[ -n "$OUT_FILE" ] || OUT_FILE="$(pwd)/$FOUNDATION.pack.txt"
|
|
70
|
+
|
|
71
|
+
# -------------------------------------------------------------- exclusions ---
|
|
72
|
+
# Entire subtree excluded if any path segment matches one of these.
|
|
73
|
+
# UI additions over VerticalSlice: .turbo, dist, out, coverage, .cache
|
|
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
|
+
# Files excluded by leaf-name glob.
|
|
81
|
+
# NOTE: the root '.env' is intentionally NOT excluded — it carries required sample config.
|
|
82
|
+
EXCLUDE_FILES=(
|
|
83
|
+
'*.user' '*.suo' '*.DotSettings.user' '*.db' '*.db-shm' '*.db-wal' '*.sqlite' '*.log'
|
|
84
|
+
'.env.*' '*.pfx' '*.pem' '*.key' '*.snk' 'id_rsa' 'id_rsa.pub' 'secrets.json'
|
|
85
|
+
'.DS_Store' # macOS metadata
|
|
86
|
+
'*.tsbuildinfo' # TypeScript incremental build cache
|
|
87
|
+
'next-env.d.ts' # Next.js auto-generated — regenerated on first npm run dev
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Content patterns that indicate a real secret; packing aborts on a hit unless --allow-secrets.
|
|
91
|
+
SECRET_NAMES=('NuGet ClearTextPassword' 'Private key block' 'AWS access key id' 'Azure DevOps PAT-ish')
|
|
92
|
+
SECRET_RX=(
|
|
93
|
+
'ClearTextPassword'
|
|
94
|
+
'BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY'
|
|
95
|
+
'AKIA[0-9A-Z]{16}'
|
|
96
|
+
'[A-Za-z0-9]{52}AZDO[A-Za-z0-9]'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
is_excluded() { # $1 = repo-relative path with '/' separators
|
|
100
|
+
local rel="$1" seg leaf g
|
|
101
|
+
local -a segs
|
|
102
|
+
IFS='/' read -ra segs <<< "$rel"
|
|
103
|
+
for seg in "${segs[@]}"; do
|
|
104
|
+
for g in "${EXCLUDE_DIRS[@]}"; do
|
|
105
|
+
[ "$seg" = "$g" ] && return 0
|
|
106
|
+
done
|
|
107
|
+
done
|
|
108
|
+
leaf="${rel##*/}"
|
|
109
|
+
for g in "${EXCLUDE_FILES[@]}"; do
|
|
110
|
+
# shellcheck disable=SC2254
|
|
111
|
+
case "$leaf" in $g) return 0 ;; esac
|
|
112
|
+
done
|
|
113
|
+
return 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
is_binary() {
|
|
117
|
+
local f="$1" n1 n2
|
|
118
|
+
n1="$(head -c 8000 "$f" | wc -c | tr -d '[:space:]')"
|
|
119
|
+
n2="$(head -c 8000 "$f" | LC_ALL=C tr -d '\000' | wc -c | tr -d '[:space:]')"
|
|
120
|
+
[ "$n1" != "$n2" ]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ----------------------------------------------------------- collect + sort ---
|
|
124
|
+
picked=()
|
|
125
|
+
while IFS= read -r full; do
|
|
126
|
+
rel="${full#"$SRC"/}"
|
|
127
|
+
is_excluded "$rel" && continue
|
|
128
|
+
picked+=("$rel")
|
|
129
|
+
done < <(find "$SRC" -type f | LC_ALL=C sort)
|
|
130
|
+
|
|
131
|
+
[ "${#picked[@]}" -gt 0 ] || { echo "Error: No files to pack under: $SRC" >&2; exit 1; }
|
|
132
|
+
|
|
133
|
+
# ----------------------------------------------------------- secret scan -----
|
|
134
|
+
findings=()
|
|
135
|
+
for rel in "${picked[@]}"; do
|
|
136
|
+
f="$SRC/$rel"
|
|
137
|
+
for i in "${!SECRET_RX[@]}"; do
|
|
138
|
+
if LC_ALL=C grep -aEq -- "${SECRET_RX[$i]}" "$f" 2>/dev/null; then
|
|
139
|
+
findings+=(" $rel -> ${SECRET_NAMES[$i]}")
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
done
|
|
143
|
+
if [ "${#findings[@]}" -gt 0 ]; then
|
|
144
|
+
if [ "$ALLOW_SECRETS" -eq 1 ]; then
|
|
145
|
+
echo "WARNING: Potential secret(s) detected — packing anyway (--allow-secrets):" >&2
|
|
146
|
+
printf '%s\n' "${findings[@]}" >&2
|
|
147
|
+
else
|
|
148
|
+
echo "Error: Potential secret(s) detected — refusing to pack:" >&2
|
|
149
|
+
printf '%s\n' "${findings[@]}" >&2
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ----------------------------------------------------------- sentinel check --
|
|
155
|
+
hits=0
|
|
156
|
+
for rel in "${picked[@]}"; do
|
|
157
|
+
if LC_ALL=C grep -aFq -- "$SENTINEL" "$SRC/$rel" 2>/dev/null; then
|
|
158
|
+
hits=$((hits + 1))
|
|
159
|
+
fi
|
|
160
|
+
done
|
|
161
|
+
if [ "$hits" -eq 0 ]; then
|
|
162
|
+
echo "WARNING: Sentinel '$SENTINEL' not found in any file. Did you author the template under that exact name?" >&2
|
|
163
|
+
else
|
|
164
|
+
echo "Sentinel '$SENTINEL' found in $hits file(s)."
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# ----------------------------------------------------------- write the pack --
|
|
168
|
+
outdir="$(dirname "$OUT_FILE")"
|
|
169
|
+
[ -d "$outdir" ] || mkdir -p "$outdir"
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
printf '%s\n' '# AW-SPEC FOUNDATION PACK'
|
|
173
|
+
printf '# foundation: %s\n' "$FOUNDATION"
|
|
174
|
+
printf '# sentinel: %s\n' "$SENTINEL"
|
|
175
|
+
printf '# generated: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%S.0000000Z)"
|
|
176
|
+
printf '# files: %s\n' "${#picked[@]}"
|
|
177
|
+
printf '%s\n' '# format: aw-spec-pack/v1'
|
|
178
|
+
printf '%s\n' '# --- replacement contract (ordinal, case-sensitive, longest-first) ---'
|
|
179
|
+
printf '%s\n' '# <Sentinel> -> <AppName> (dotted: package.json name as dotted)'
|
|
180
|
+
printf '%s\n' '# <Sentinel _-form> -> <AppName _-form> (underscored: identifiers)'
|
|
181
|
+
printf '%s\n' '# <sentinel --form> -> <appname --form> (lower-hyphen: npm package name, docker image)'
|
|
182
|
+
printf '%s\n' '# <sentinel .-form> -> <appname lower-dot> (lower-dot: lowercased identifiers)'
|
|
183
|
+
printf '%s\n' '# Applied to file CONTENTS and file/dir PATHS. Never touch other AppWeaver.* tokens.'
|
|
184
|
+
printf '%s\n' '# ============================================================'
|
|
185
|
+
printf '\n'
|
|
186
|
+
} > "$OUT_FILE"
|
|
187
|
+
|
|
188
|
+
for rel in "${picked[@]}"; do
|
|
189
|
+
f="$SRC/$rel"
|
|
190
|
+
if is_binary "$f"; then
|
|
191
|
+
{
|
|
192
|
+
printf '<<<FILE path="%s" encoding="base64">>>\n' "$rel"
|
|
193
|
+
base64 < "$f" | tr -d '\n'
|
|
194
|
+
printf '\n'
|
|
195
|
+
printf '%s\n' '<<<ENDFILE>>>'
|
|
196
|
+
} >> "$OUT_FILE"
|
|
197
|
+
else
|
|
198
|
+
{
|
|
199
|
+
printf '<<<FILE path="%s">>>\n' "$rel"
|
|
200
|
+
awk '{ sub(/\r$/, ""); print }' "$f"
|
|
201
|
+
printf '%s\n' '<<<ENDFILE>>>'
|
|
202
|
+
} >> "$OUT_FILE"
|
|
203
|
+
fi
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
echo "Packed ${#picked[@]} file(s) -> $OUT_FILE"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Pack a verified UI feature reference template into a single AW-Spec text pack file.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Walks -SourceDir (a templates/features/<Name>.SimpleSample folder for UI features),
|
|
8
|
+
excludes build/IDE artifacts, JS output, template meta-files, and secret-bearing files,
|
|
9
|
+
and writes every remaining file into one pack file using <<<FILE>>> / <<<ENDFILE>>>
|
|
10
|
+
markers. Text files are stored verbatim (UTF-8, LF); binary files are base64-encoded.
|
|
11
|
+
The resulting pack file is consumed by /aw-spec-addcomponent or /aw-spec-implement
|
|
12
|
+
unpack workflows.
|
|
13
|
+
|
|
14
|
+
This is the UI counterpart of pack-reference-foundation.ps1 in docs/VerticalSlice/.
|
|
15
|
+
|
|
16
|
+
.EXAMPLE
|
|
17
|
+
./pack-reference-foundation.ps1 `
|
|
18
|
+
-SourceDir ../../../../templates/features/UIVisualization.SimpleSample `
|
|
19
|
+
-OutFile ./UI.Visualization.Reference.pack.txt `
|
|
20
|
+
-Sentinel UI.Visualization `
|
|
21
|
+
-Foundation UI.Visualization.Reference
|
|
22
|
+
#>
|
|
23
|
+
[CmdletBinding()]
|
|
24
|
+
param(
|
|
25
|
+
[Parameter(Mandatory)] [string] $SourceDir,
|
|
26
|
+
[string] $OutFile,
|
|
27
|
+
[string] $Sentinel = 'UI.Visualization',
|
|
28
|
+
[string] $Foundation = 'UI.Visualization.Reference',
|
|
29
|
+
[switch] $AllowSecrets
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
$ErrorActionPreference = 'Stop'
|
|
33
|
+
|
|
34
|
+
$src = (Resolve-Path -LiteralPath $SourceDir).Path
|
|
35
|
+
if (-not (Test-Path -LiteralPath $src -PathType Container)) {
|
|
36
|
+
throw "SourceDir is not a folder: $SourceDir"
|
|
37
|
+
}
|
|
38
|
+
if (-not $OutFile) { $OutFile = Join-Path (Get-Location).Path "$Foundation.pack.txt" }
|
|
39
|
+
|
|
40
|
+
# Entire subtree excluded if any path segment matches one of these.
|
|
41
|
+
$excludeDirs = @(
|
|
42
|
+
'bin','obj','.vs','.git','.idea','.vscode','TestResults',
|
|
43
|
+
'node_modules','packages','App_Data','artifacts',
|
|
44
|
+
'.next','.turbo','dist','out','coverage','.cache','logs'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Files excluded by leaf-name glob — build noise, local dbs, secret-bearing files,
|
|
48
|
+
# macOS metadata, and template meta-files that must never appear in unpacked output.
|
|
49
|
+
$excludeFiles = @(
|
|
50
|
+
'*.user','*.suo','*.DotSettings.user','*.db','*.db-shm','*.db-wal','*.sqlite','*.log',
|
|
51
|
+
'.env','*.env.*','.env.*','*.pfx','*.pem','*.key','*.snk','id_rsa','id_rsa.pub','secrets.json',
|
|
52
|
+
'.DS_Store',
|
|
53
|
+
'*.my-ref.*', # template annotation files
|
|
54
|
+
'*TemplateConfig.config',
|
|
55
|
+
'*.tsbuildinfo',
|
|
56
|
+
'next-env.d.ts'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Content patterns that indicate a real secret.
|
|
60
|
+
$secretPatterns = @(
|
|
61
|
+
@{ Name = 'NuGet ClearTextPassword'; Rx = 'ClearTextPassword' }
|
|
62
|
+
@{ Name = 'Private key block'; Rx = 'BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY' }
|
|
63
|
+
@{ Name = 'AWS access key id'; Rx = 'AKIA[0-9A-Z]{16}' }
|
|
64
|
+
@{ Name = 'Azure DevOps PAT-ish'; Rx = '[A-Za-z0-9]{52}AZDO[A-Za-z0-9]' }
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
function Test-Excluded([string] $relPath) {
|
|
68
|
+
foreach ($seg in ($relPath -split '[\\/]')) {
|
|
69
|
+
if ($excludeDirs -contains $seg) { return $true }
|
|
70
|
+
}
|
|
71
|
+
$leaf = Split-Path $relPath -Leaf
|
|
72
|
+
foreach ($g in $excludeFiles) { if ($leaf -like $g) { return $true } }
|
|
73
|
+
return $false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Test-Binary([byte[]] $bytes) {
|
|
77
|
+
$n = [Math]::Min(8000, $bytes.Length)
|
|
78
|
+
for ($i = 0; $i -lt $n; $i++) { if ($bytes[$i] -eq 0) { return $true } }
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Collect + filter
|
|
83
|
+
$picked = New-Object System.Collections.Generic.List[object]
|
|
84
|
+
foreach ($f in (Get-ChildItem -LiteralPath $src -Recurse -File -Force)) {
|
|
85
|
+
$rel = ($f.FullName.Substring($src.Length).TrimStart('\','/')) -replace '\\','/'
|
|
86
|
+
if (Test-Excluded $rel) { continue }
|
|
87
|
+
$picked.Add([pscustomobject]@{ Rel = $rel; Full = $f.FullName })
|
|
88
|
+
}
|
|
89
|
+
$picked = @($picked | Sort-Object Rel)
|
|
90
|
+
if ($picked.Count -eq 0) { throw "No files to pack under: $src" }
|
|
91
|
+
|
|
92
|
+
# Secret scan
|
|
93
|
+
$findings = New-Object System.Collections.Generic.List[string]
|
|
94
|
+
foreach ($p in $picked) {
|
|
95
|
+
try { $t = [System.IO.File]::ReadAllText($p.Full) } catch { continue }
|
|
96
|
+
foreach ($pat in $secretPatterns) {
|
|
97
|
+
if ($t -match $pat.Rx) { $findings.Add(" $($p.Rel) -> $($pat.Name)") }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if ($findings.Count -gt 0) {
|
|
101
|
+
$msg = "Potential secret(s) detected — refusing to pack (rotate + remove them, or pass -AllowSecrets):`n" + ($findings -join "`n")
|
|
102
|
+
if ($AllowSecrets) { Write-Warning $msg } else { throw $msg }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Sentinel sanity check
|
|
106
|
+
$hits = 0
|
|
107
|
+
foreach ($p in $picked) {
|
|
108
|
+
try {
|
|
109
|
+
$t = [System.IO.File]::ReadAllText($p.Full)
|
|
110
|
+
if ($t.Contains($Sentinel)) { $hits++ }
|
|
111
|
+
} catch { }
|
|
112
|
+
}
|
|
113
|
+
if ($hits -eq 0) {
|
|
114
|
+
Write-Warning "Sentinel '$Sentinel' not found in any file. Did you author the sample under that exact name?"
|
|
115
|
+
} else {
|
|
116
|
+
Write-Host "Sentinel '$Sentinel' found in $hits file(s)."
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Build the pack
|
|
120
|
+
$sb = [System.Text.StringBuilder]::new()
|
|
121
|
+
function AddLine([string] $s) { [void]$sb.Append($s); [void]$sb.Append("`n") }
|
|
122
|
+
|
|
123
|
+
AddLine '# AW-SPEC REFERENCE PACK'
|
|
124
|
+
AddLine "# foundation: $Foundation"
|
|
125
|
+
AddLine "# sentinel: $Sentinel"
|
|
126
|
+
AddLine "# generated: $([DateTime]::UtcNow.ToString('o'))"
|
|
127
|
+
AddLine "# files: $($picked.Count)"
|
|
128
|
+
AddLine '# format: aw-spec-pack/v1'
|
|
129
|
+
AddLine '# --- replacement contract (ordinal, case-sensitive, longest-first) ---'
|
|
130
|
+
AddLine '# <Sentinel> -> <AppName> (dotted: package.json name as dotted)'
|
|
131
|
+
AddLine '# <Sentinel _-form> -> <AppName _-form> (underscored: identifiers)'
|
|
132
|
+
AddLine '# <sentinel --form> -> <appname --form> (lower-hyphen: npm package name, docker image)'
|
|
133
|
+
AddLine '# <sentinel .-form> -> <appname lower-dot> (lower-dot: lowercased identifiers)'
|
|
134
|
+
AddLine '# Applied to file CONTENTS and file/dir PATHS. Never touch other AppWeaver.* tokens.'
|
|
135
|
+
AddLine '# ============================================================'
|
|
136
|
+
AddLine ''
|
|
137
|
+
|
|
138
|
+
foreach ($p in $picked) {
|
|
139
|
+
$bytes = [System.IO.File]::ReadAllBytes($p.Full)
|
|
140
|
+
if (Test-Binary $bytes) {
|
|
141
|
+
AddLine "<<<FILE path=`"$($p.Rel)`" encoding=`"base64`">>>"
|
|
142
|
+
AddLine ([Convert]::ToBase64String($bytes))
|
|
143
|
+
AddLine '<<<ENDFILE>>>'
|
|
144
|
+
} else {
|
|
145
|
+
$text = ([System.IO.File]::ReadAllText($p.Full)) -replace "`r`n", "`n"
|
|
146
|
+
AddLine "<<<FILE path=`"$($p.Rel)`">>>"
|
|
147
|
+
[void]$sb.Append($text)
|
|
148
|
+
if (-not $text.EndsWith("`n")) { [void]$sb.Append("`n") }
|
|
149
|
+
AddLine '<<<ENDFILE>>>'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
$outDir = Split-Path -Parent $OutFile
|
|
154
|
+
if ($outDir -and -not (Test-Path -LiteralPath $outDir)) {
|
|
155
|
+
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
|
156
|
+
}
|
|
157
|
+
[System.IO.File]::WriteAllText($OutFile, $sb.ToString(), [System.Text.UTF8Encoding]::new($false))
|
|
158
|
+
|
|
159
|
+
Write-Host "Packed $($picked.Count) file(s) -> $OutFile"
|