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.
Files changed (2) hide show
  1. package/build.sh +274 -0
  2. 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.0",
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
  ],