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