sloss-cli 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.sh +274 -0
- package/package.json +2 -1
package/build.sh
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sloss build — Local EAS build + Sloss upload for Expo apps
|
|
3
|
+
# Invoked by the `sloss build` Node CLI command.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# ─── Defaults ───────────────────────────────────────────────────────────────
|
|
7
|
+
PLATFORM="ios"
|
|
8
|
+
PROFILE="development"
|
|
9
|
+
BUMP="patch"
|
|
10
|
+
PROJECT_DIR="$(pwd)"
|
|
11
|
+
YES=false
|
|
12
|
+
|
|
13
|
+
# ─── Flag parsing ───────────────────────────────────────────────────────────
|
|
14
|
+
while [[ $# -gt 0 ]]; do
|
|
15
|
+
case "$1" in
|
|
16
|
+
--platform) PLATFORM="$2"; shift 2 ;;
|
|
17
|
+
--profile) PROFILE="$2"; shift 2 ;;
|
|
18
|
+
--bump) BUMP="$2"; shift 2 ;;
|
|
19
|
+
--dir) PROJECT_DIR="$2"; shift 2 ;;
|
|
20
|
+
--yes|-y) YES=true; shift ;;
|
|
21
|
+
--help|-h)
|
|
22
|
+
echo "Usage: sloss build [options]"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "Options:"
|
|
25
|
+
echo " --platform ios|android Target platform (default: ios)"
|
|
26
|
+
echo " --profile <name> EAS build profile (default: development)"
|
|
27
|
+
echo " --bump patch|minor|major Version bump type (default: patch)"
|
|
28
|
+
echo " --dir <path> Project directory (default: cwd)"
|
|
29
|
+
echo " --yes, -y Skip confirmation prompt"
|
|
30
|
+
echo " --help, -h Show this help"
|
|
31
|
+
exit 0
|
|
32
|
+
;;
|
|
33
|
+
*) echo "Unknown flag: $1"; exit 1 ;;
|
|
34
|
+
esac
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
# ─── Helpers ────────────────────────────────────────────────────────────────
|
|
38
|
+
# Use node for all JSON parsing (jq not guaranteed).
|
|
39
|
+
json_get() {
|
|
40
|
+
# json_get <file> <key> — read a top-level string value
|
|
41
|
+
node -e "const f=require('fs').readFileSync('$1','utf8'); console.log(JSON.parse(f)['$2'])"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
json_set() {
|
|
45
|
+
# json_set <file> <key> <value> — set a top-level string value, preserve formatting
|
|
46
|
+
node -e "
|
|
47
|
+
const fs=require('fs'), p='$1', k='$2', v='$3';
|
|
48
|
+
const obj=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
49
|
+
obj[k]=v;
|
|
50
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2)+'\n');
|
|
51
|
+
"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
json_nested() {
|
|
55
|
+
# json_nested <file> <path.to.key> — read a nested value via dot path
|
|
56
|
+
node -e "
|
|
57
|
+
const fs=require('fs'), p='$1', keys='$2'.split('.');
|
|
58
|
+
let obj=JSON.parse(fs.readFileSync(p,'utf8'));
|
|
59
|
+
for(const k of keys) obj=obj[k];
|
|
60
|
+
console.log(obj);
|
|
61
|
+
"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
plist_set() {
|
|
65
|
+
# plist_set <file> <key> <value> — update a value in an XML plist
|
|
66
|
+
/usr/libexec/PlistBuddy -c "Set :$2 $3" "$1" 2>/dev/null || \
|
|
67
|
+
sed -i '' "s|<key>$2</key>.*<string>[^<]*</string>|<key>$2</key>\n\t<string>$3</string>|" "$1"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
bump_version() {
|
|
71
|
+
# bump_version <current> <patch|minor|major> → new version string
|
|
72
|
+
node -e "
|
|
73
|
+
const [M,m,p]='$1'.split('.').map(Number);
|
|
74
|
+
const t='$2';
|
|
75
|
+
if(t==='major') console.log((M+1)+'.0.0');
|
|
76
|
+
else if(t==='minor') console.log(M+'.'+(m+1)+'.0');
|
|
77
|
+
else console.log(M+'.'+m+'.'+(p+1));
|
|
78
|
+
"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ─── Validate inputs ───────────────────────────────────────────────────────
|
|
82
|
+
if [[ "$PLATFORM" != "ios" && "$PLATFORM" != "android" ]]; then
|
|
83
|
+
echo "Error: --platform must be ios or android"; exit 1
|
|
84
|
+
fi
|
|
85
|
+
if [[ "$BUMP" != "patch" && "$BUMP" != "minor" && "$BUMP" != "major" ]]; then
|
|
86
|
+
echo "Error: --bump must be patch, minor, or major"; exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# ─── Read .sloss.json ──────────────────────────────────────────────────────
|
|
90
|
+
CONFIG="$PROJECT_DIR/.sloss.json"
|
|
91
|
+
if [[ ! -f "$CONFIG" ]]; then
|
|
92
|
+
echo "Error: No .sloss.json found in $PROJECT_DIR"
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
APP_NAME=$(json_get "$CONFIG" "app_name")
|
|
97
|
+
BUNDLE_ID=$(json_get "$CONFIG" "bundle_id")
|
|
98
|
+
VERSION_FILE="$PROJECT_DIR/$(json_get "$CONFIG" "version_file")"
|
|
99
|
+
IOS_PLIST="$PROJECT_DIR/$(json_get "$CONFIG" "ios_plist")"
|
|
100
|
+
BUNDLE_SUFFIX=$(json_nested "$CONFIG" "profiles.$PROFILE.bundle_id_suffix")
|
|
101
|
+
|
|
102
|
+
FULL_BUNDLE_ID="${BUNDLE_ID}${BUNDLE_SUFFIX}"
|
|
103
|
+
|
|
104
|
+
# ─── Read current version info ─────────────────────────────────────────────
|
|
105
|
+
CURRENT_VERSION=$(json_get "$VERSION_FILE" "version")
|
|
106
|
+
CURRENT_BUILD=$(json_get "$VERSION_FILE" "buildNumber")
|
|
107
|
+
NEW_BUILD=$((CURRENT_BUILD + 1))
|
|
108
|
+
|
|
109
|
+
# Marketing version bump (production only)
|
|
110
|
+
if [[ "$PROFILE" == "production" ]]; then
|
|
111
|
+
NEW_VERSION=$(bump_version "$CURRENT_VERSION" "$BUMP")
|
|
112
|
+
else
|
|
113
|
+
NEW_VERSION="$CURRENT_VERSION"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# ─── Confirmation ───────────────────────────────────────────────────────────
|
|
117
|
+
echo ""
|
|
118
|
+
echo "╔══════════════════════════════════════════╗"
|
|
119
|
+
echo "║ Sloss Build Summary ║"
|
|
120
|
+
echo "╠══════════════════════════════════════════╣"
|
|
121
|
+
echo "║ App: $APP_NAME"
|
|
122
|
+
echo "║ Bundle ID: $FULL_BUNDLE_ID"
|
|
123
|
+
echo "║ Platform: $PLATFORM"
|
|
124
|
+
echo "║ Profile: $PROFILE"
|
|
125
|
+
echo "║ Version: $CURRENT_VERSION → $NEW_VERSION"
|
|
126
|
+
echo "║ Build: $CURRENT_BUILD → $NEW_BUILD"
|
|
127
|
+
echo "╚══════════════════════════════════════════╝"
|
|
128
|
+
echo ""
|
|
129
|
+
|
|
130
|
+
if [[ "$YES" != true ]]; then
|
|
131
|
+
read -r -p "Proceed? [y/N] " confirm
|
|
132
|
+
if [[ "$confirm" != [yY] && "$confirm" != [yY][eE][sS] ]]; then
|
|
133
|
+
echo "Aborted."
|
|
134
|
+
exit 0
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# ─── Bump versions ─────────────────────────────────────────────────────────
|
|
139
|
+
echo "📦 Bumping build number: $CURRENT_BUILD → $NEW_BUILD"
|
|
140
|
+
json_set "$VERSION_FILE" "buildNumber" "$NEW_BUILD"
|
|
141
|
+
|
|
142
|
+
# Update Info.plist CFBundleVersion
|
|
143
|
+
if [[ -f "$IOS_PLIST" ]]; then
|
|
144
|
+
plist_set "$IOS_PLIST" "CFBundleVersion" "$NEW_BUILD"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [[ "$PROFILE" == "production" ]]; then
|
|
148
|
+
echo "📦 Bumping marketing version: $CURRENT_VERSION → $NEW_VERSION ($BUMP)"
|
|
149
|
+
json_set "$VERSION_FILE" "version" "$NEW_VERSION"
|
|
150
|
+
if [[ -f "$IOS_PLIST" ]]; then
|
|
151
|
+
plist_set "$IOS_PLIST" "CFBundleShortVersionString" "$NEW_VERSION"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# ─── Run EAS build ──────────────────────────────────────────────────────────
|
|
156
|
+
echo ""
|
|
157
|
+
echo "🔨 Starting EAS local build..."
|
|
158
|
+
echo " eas build --profile $PROFILE --platform $PLATFORM --local --non-interactive"
|
|
159
|
+
echo ""
|
|
160
|
+
|
|
161
|
+
BUILD_LOG=$(mktemp)
|
|
162
|
+
ARTIFACT_PATH=""
|
|
163
|
+
|
|
164
|
+
# Run build, tee output so user sees it and we can parse it
|
|
165
|
+
set +e
|
|
166
|
+
(cd "$PROJECT_DIR" && eas build --profile "$PROFILE" --platform "$PLATFORM" --local --non-interactive 2>&1) | tee "$BUILD_LOG"
|
|
167
|
+
BUILD_EXIT=${PIPESTATUS[0]}
|
|
168
|
+
set -e
|
|
169
|
+
|
|
170
|
+
if [[ $BUILD_EXIT -ne 0 ]]; then
|
|
171
|
+
echo ""
|
|
172
|
+
echo "❌ EAS build failed (exit code $BUILD_EXIT). Skipping upload."
|
|
173
|
+
rm -f "$BUILD_LOG"
|
|
174
|
+
exit 1
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# ─── Find artifact ──────────────────────────────────────────────────────────
|
|
178
|
+
# EAS outputs lines like:
|
|
179
|
+
# "Build artifact: /path/to/build-xxxx.ipa"
|
|
180
|
+
# or the artifact path on its own line ending in .ipa/.apk
|
|
181
|
+
if [[ "$PLATFORM" == "ios" ]]; then
|
|
182
|
+
EXT="ipa"
|
|
183
|
+
else
|
|
184
|
+
EXT="apk"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
ARTIFACT_PATH=$(grep -oE '/[^ ]+\.'$EXT "$BUILD_LOG" | tail -1 || true)
|
|
188
|
+
|
|
189
|
+
# Fallback: check common EAS output directory
|
|
190
|
+
if [[ -z "$ARTIFACT_PATH" || ! -f "$ARTIFACT_PATH" ]]; then
|
|
191
|
+
ARTIFACT_PATH=$(find "$PROJECT_DIR" -maxdepth 1 -name "*.$EXT" -newer "$VERSION_FILE" -print -quit 2>/dev/null || true)
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
rm -f "$BUILD_LOG"
|
|
195
|
+
|
|
196
|
+
if [[ -z "$ARTIFACT_PATH" || ! -f "$ARTIFACT_PATH" ]]; then
|
|
197
|
+
echo ""
|
|
198
|
+
echo "⚠️ Could not locate build artifact. Check the build output above."
|
|
199
|
+
echo " Expected a .$EXT file."
|
|
200
|
+
exit 1
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
echo ""
|
|
204
|
+
echo "✅ Build artifact: $ARTIFACT_PATH"
|
|
205
|
+
|
|
206
|
+
# ─── Upload to Sloss ───────────────────────────────────────────────────────
|
|
207
|
+
echo ""
|
|
208
|
+
echo "📤 Uploading to Sloss..."
|
|
209
|
+
|
|
210
|
+
# Resolve Sloss URL and API key
|
|
211
|
+
UPLOAD_URL="${SLOSS_URL:-https://sloss.ngrok.app}/upload"
|
|
212
|
+
|
|
213
|
+
if [[ -z "${SLOSS_API_KEY:-}" ]]; then
|
|
214
|
+
CREDS_FILE="$HOME/.config/sloss/credentials.json"
|
|
215
|
+
if [[ -f "$CREDS_FILE" ]]; then
|
|
216
|
+
SLOSS_API_KEY=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$CREDS_FILE','utf8')).api_key)")
|
|
217
|
+
else
|
|
218
|
+
echo "❌ No SLOSS_API_KEY env var and no ~/.config/sloss/credentials.json found."
|
|
219
|
+
echo " Artifact is at: $ARTIFACT_PATH"
|
|
220
|
+
exit 1
|
|
221
|
+
fi
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Upload — field name is 'ipa' for iOS, 'apk' for Android
|
|
225
|
+
FIELD_NAME="$EXT"
|
|
226
|
+
set +e
|
|
227
|
+
UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
228
|
+
-H "Authorization: Bearer $SLOSS_API_KEY" \
|
|
229
|
+
-F "${FIELD_NAME}=@${ARTIFACT_PATH}" \
|
|
230
|
+
-F "app_name=${APP_NAME}" \
|
|
231
|
+
-F "bundle_id=${FULL_BUNDLE_ID}" \
|
|
232
|
+
-F "version=${NEW_VERSION}" \
|
|
233
|
+
-F "build_number=${NEW_BUILD}" \
|
|
234
|
+
-F "profile=${PROFILE}" \
|
|
235
|
+
"$UPLOAD_URL")
|
|
236
|
+
UPLOAD_EXIT=$?
|
|
237
|
+
set -e
|
|
238
|
+
|
|
239
|
+
if [[ $UPLOAD_EXIT -ne 0 ]]; then
|
|
240
|
+
echo "❌ Upload failed (curl error)."
|
|
241
|
+
echo " Artifact is at: $ARTIFACT_PATH"
|
|
242
|
+
exit 1
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
HTTP_CODE=$(echo "$UPLOAD_RESPONSE" | tail -1)
|
|
246
|
+
RESPONSE_BODY=$(echo "$UPLOAD_RESPONSE" | sed '$d')
|
|
247
|
+
|
|
248
|
+
if [[ "$HTTP_CODE" -lt 200 || "$HTTP_CODE" -ge 300 ]]; then
|
|
249
|
+
echo "❌ Upload failed (HTTP $HTTP_CODE)."
|
|
250
|
+
echo " $RESPONSE_BODY"
|
|
251
|
+
echo " Artifact is at: $ARTIFACT_PATH"
|
|
252
|
+
exit 1
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# ─── Print results ──────────────────────────────────────────────────────────
|
|
256
|
+
PAGE_URL=$(node -e "try{console.log(JSON.parse(process.argv[1]).page_url||'')}catch{console.log('')}" "$RESPONSE_BODY")
|
|
257
|
+
INSTALL_URL=$(node -e "try{console.log(JSON.parse(process.argv[1]).install_url||'')}catch{console.log('')}" "$RESPONSE_BODY")
|
|
258
|
+
|
|
259
|
+
echo ""
|
|
260
|
+
echo "╔══════════════════════════════════════════╗"
|
|
261
|
+
echo "║ 🎉 Build Complete! ║"
|
|
262
|
+
echo "╠══════════════════════════════════════════╣"
|
|
263
|
+
echo "║ App: $APP_NAME"
|
|
264
|
+
echo "║ Version: $NEW_VERSION ($NEW_BUILD)"
|
|
265
|
+
echo "║ Profile: $PROFILE"
|
|
266
|
+
echo "║ Platform: $PLATFORM"
|
|
267
|
+
echo "║ Artifact: $ARTIFACT_PATH"
|
|
268
|
+
if [[ -n "$PAGE_URL" ]]; then
|
|
269
|
+
echo "║ Page: $PAGE_URL"
|
|
270
|
+
fi
|
|
271
|
+
if [[ -n "$INSTALL_URL" ]]; then
|
|
272
|
+
echo "║ Install: $INSTALL_URL"
|
|
273
|
+
fi
|
|
274
|
+
echo "╚══════════════════════════════════════════╝"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sloss-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "CLI for Sloss — a self-hosted IPA/APK distribution server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
11
|
"src/",
|
|
12
|
+
"build.sh",
|
|
12
13
|
"skills/",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|