appship-cli 0.1.0 → 0.1.2
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 +3 -1
- package/dist/{chunk-7GCQ6I27.js → chunk-AZTXBAUU.js} +46 -9
- package/dist/{chunk-TLOV7VBN.js → chunk-D5IVOO3T.js} +1 -1
- package/dist/{chunk-CVYD5FVI.js → chunk-XGVCTALD.js} +1 -1
- package/dist/{flutter-ITOL6J4H.js → flutter-UIBWJIKY.js} +2 -2
- package/dist/index.js +9 -7
- package/dist/{native-RCWBM6UF.js → native-WXU3IQKR.js} +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Ship your mobile app without spending hours inside App Store Connect and Google Play Console.
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
**Status: MVP 3 implemented (early development).** React Native, Expo, Flutter, native iOS, and native Android projects. See [docs/PRD.md](./docs/PRD.md) and [docs/TRD.md](./docs/TRD.md) for the full plan.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
@@ -51,7 +53,7 @@ Store policies change between releases, so the doctor rules and SDK signature da
|
|
|
51
53
|
|
|
52
54
|
`generate`, `localize`, and `review analyze` call an AI provider — Anthropic by default (`ANTHROPIC_API_KEY` or `ant auth login`), and any OpenAI-compatible endpoint via `ai.provider` in appship.yml: `gemini` (free keys at [aistudio.google.com](https://aistudio.google.com/apikey), `GEMINI_API_KEY`, default model `gemini-2.5-flash`), `openai` (`OPENAI_API_KEY`), `ollama` (local, set `ai.model`), or `openai-compatible` (set `ai.base_url` + `ai.model` — works with OpenRouter, Groq, and self-hosted servers). Store character limits are validated with retry regardless of provider. `init` and `doctor` run fully offline. Translations go through the same validation as generation; App Review notes are copied, not translated.
|
|
53
55
|
|
|
54
|
-
Supported projects: React Native CLI, Expo (managed), Flutter, native iOS (`.xcodeproj` at the root), and native Android (`settings.gradle` + `app/` module) — identity and permissions are read from native files (`Info.plist`, `AndroidManifest.xml`, `project.pbxproj`, `build.gradle`), from `app.json`
|
|
56
|
+
Supported projects: React Native CLI, Expo (managed), Flutter, native iOS (`.xcodeproj` at the root), and native Android (`settings.gradle` + `app/` module) — identity and permissions are read from native files (`Info.plist`, `AndroidManifest.xml`, `project.pbxproj`, `build.gradle`), from the Expo config (`app.json` or a dynamic `app.config.js`/`.cjs`/`.mjs`, evaluated the same way Expo CLI does), or from `pubspec.yaml`. SDK detection matches npm packages, Dart/Flutter packages, CocoaPods, and Gradle Maven coordinates, plus source patterns in JS/TS, Dart, Swift, Kotlin, and Java.
|
|
55
57
|
|
|
56
58
|
## Using doctor as a CI gate
|
|
57
59
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
APPSHIP_VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XGVCTALD.js";
|
|
5
5
|
|
|
6
6
|
// src/core/project/flutter.ts
|
|
7
7
|
import { readFile as readFile4 } from "fs/promises";
|
|
@@ -32,13 +32,13 @@ async function detectProjectType(projectRoot) {
|
|
|
32
32
|
if (pkg && "react-native" in deps) {
|
|
33
33
|
return "react-native";
|
|
34
34
|
}
|
|
35
|
-
const { readPubspec: readPubspec2, isFlutterPubspec: isFlutterPubspec2 } = await import("./flutter-
|
|
35
|
+
const { readPubspec: readPubspec2, isFlutterPubspec: isFlutterPubspec2 } = await import("./flutter-UIBWJIKY.js");
|
|
36
36
|
if (isFlutterPubspec2(await readPubspec2(projectRoot))) {
|
|
37
37
|
return "flutter";
|
|
38
38
|
}
|
|
39
|
-
const { findRootPbxproj, hasNativeAndroidLayout } = await import("./native-
|
|
40
|
-
const { existsSync } = await import("fs");
|
|
41
|
-
if (hasNativeAndroidLayout(projectRoot,
|
|
39
|
+
const { findRootPbxproj, hasNativeAndroidLayout } = await import("./native-WXU3IQKR.js");
|
|
40
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
41
|
+
if (hasNativeAndroidLayout(projectRoot, existsSync2)) {
|
|
42
42
|
return "native-android";
|
|
43
43
|
}
|
|
44
44
|
if ((await findRootPbxproj(projectRoot)).length > 0) {
|
|
@@ -51,9 +51,11 @@ async function detectProjectType(projectRoot) {
|
|
|
51
51
|
|
|
52
52
|
// src/core/project/expo.ts
|
|
53
53
|
import { readFile as readFile2 } from "fs/promises";
|
|
54
|
+
import { existsSync } from "fs";
|
|
54
55
|
import { join as join2 } from "path";
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
import { pathToFileURL } from "url";
|
|
57
|
+
var DYNAMIC_CONFIG_FILES = ["app.config.js", "app.config.cjs", "app.config.mjs"];
|
|
58
|
+
async function readAppJsonExpo(projectRoot) {
|
|
57
59
|
try {
|
|
58
60
|
const raw = await readFile2(join2(projectRoot, "app.json"), "utf8");
|
|
59
61
|
const parsed = JSON.parse(raw);
|
|
@@ -62,6 +64,41 @@ async function readExpoConfig(projectRoot) {
|
|
|
62
64
|
return null;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
function unwrap(value) {
|
|
68
|
+
if (!value || typeof value !== "object") return null;
|
|
69
|
+
const record = value;
|
|
70
|
+
if (record["expo"] && typeof record["expo"] === "object") {
|
|
71
|
+
return record["expo"];
|
|
72
|
+
}
|
|
73
|
+
return record;
|
|
74
|
+
}
|
|
75
|
+
async function readExpoConfig(projectRoot) {
|
|
76
|
+
const staticConfig = await readAppJsonExpo(projectRoot);
|
|
77
|
+
for (const file of DYNAMIC_CONFIG_FILES) {
|
|
78
|
+
const path = join2(projectRoot, file);
|
|
79
|
+
if (!existsSync(path)) continue;
|
|
80
|
+
try {
|
|
81
|
+
const mod = await import(pathToFileURL(path).href);
|
|
82
|
+
let exported = mod.default ?? mod;
|
|
83
|
+
if (typeof exported === "function") {
|
|
84
|
+
exported = exported({
|
|
85
|
+
config: staticConfig ?? {}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const config = unwrap(await Promise.resolve(exported));
|
|
89
|
+
if (config) return config;
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return staticConfig;
|
|
95
|
+
}
|
|
96
|
+
function expoConfigEvidence(projectRoot) {
|
|
97
|
+
for (const file of DYNAMIC_CONFIG_FILES) {
|
|
98
|
+
if (existsSync(join2(projectRoot, file))) return `${file} (expo)`;
|
|
99
|
+
}
|
|
100
|
+
return "app.json (expo)";
|
|
101
|
+
}
|
|
65
102
|
function normalizeAndroidPermission(permission) {
|
|
66
103
|
return permission.includes(".") ? permission : `android.permission.${permission}`;
|
|
67
104
|
}
|
|
@@ -124,7 +161,7 @@ async function analyzeReactNativeProject(projectRoot) {
|
|
|
124
161
|
]);
|
|
125
162
|
return {
|
|
126
163
|
projectType: "react-native",
|
|
127
|
-
appName: appName ?? pkg?.name ?? null,
|
|
164
|
+
appName: appName ?? expo?.name ?? pkg?.name ?? null,
|
|
128
165
|
version: pkg?.version ?? expo?.version ?? null,
|
|
129
166
|
ios: { bundleId },
|
|
130
167
|
android: { packageName },
|
|
@@ -164,8 +201,8 @@ async function analyzeFlutterProject(projectRoot) {
|
|
|
164
201
|
}
|
|
165
202
|
|
|
166
203
|
export {
|
|
167
|
-
EXPO_APP_JSON_EVIDENCE,
|
|
168
204
|
readExpoConfig,
|
|
205
|
+
expoConfigEvidence,
|
|
169
206
|
normalizeAndroidPermission,
|
|
170
207
|
analyzeReactNativeProject,
|
|
171
208
|
readPubspec,
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
EXPO_APP_JSON_EVIDENCE,
|
|
4
3
|
UnsupportedProjectError,
|
|
5
4
|
analyzeFlutterProject,
|
|
6
5
|
analyzeReactNativeProject,
|
|
7
6
|
detectProjectType,
|
|
7
|
+
expoConfigEvidence,
|
|
8
8
|
normalizeAndroidPermission,
|
|
9
9
|
readExpoConfig,
|
|
10
10
|
readPackageJson,
|
|
11
11
|
readPubspec
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-AZTXBAUU.js";
|
|
13
13
|
import {
|
|
14
14
|
analyzeNativeAndroidProject,
|
|
15
15
|
analyzeNativeIosProject
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import
|
|
16
|
+
} from "./chunk-D5IVOO3T.js";
|
|
17
|
+
import {
|
|
18
|
+
APPSHIP_VERSION
|
|
19
|
+
} from "./chunk-XGVCTALD.js";
|
|
18
20
|
import {
|
|
19
21
|
UploadError,
|
|
20
22
|
assertUploadLane,
|
|
@@ -319,7 +321,7 @@ async function scanIosPermissions(projectRoot, projectType = "react-native") {
|
|
|
319
321
|
key,
|
|
320
322
|
currentMessage: message,
|
|
321
323
|
qualityAssessment: assessUsageDescription(message),
|
|
322
|
-
evidence: requireEvidence([
|
|
324
|
+
evidence: requireEvidence([expoConfigEvidence(projectRoot)], `ios permission ${key}`)
|
|
323
325
|
});
|
|
324
326
|
}
|
|
325
327
|
return [...findings.values()];
|
|
@@ -363,7 +365,7 @@ async function scanAndroidPermissions(projectRoot, projectType = "react-native")
|
|
|
363
365
|
if (findings.has(name)) continue;
|
|
364
366
|
findings.set(name, {
|
|
365
367
|
key: name,
|
|
366
|
-
evidence: requireEvidence([
|
|
368
|
+
evidence: requireEvidence([expoConfigEvidence(projectRoot)], `android permission ${name}`)
|
|
367
369
|
});
|
|
368
370
|
}
|
|
369
371
|
return [...findings.values()];
|
|
@@ -3683,7 +3685,7 @@ var rulesCommand = new Command10("rules").description("Manage the store policy r
|
|
|
3683
3685
|
var program = new Command11();
|
|
3684
3686
|
program.name("appship").description(
|
|
3685
3687
|
"AI release assistant that analyzes your mobile app and prepares everything required for App Store and Google Play submission"
|
|
3686
|
-
).version(
|
|
3688
|
+
).version(APPSHIP_VERSION);
|
|
3687
3689
|
program.addCommand(initCommand);
|
|
3688
3690
|
program.addCommand(generateCommand);
|
|
3689
3691
|
program.addCommand(localizeCommand);
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
analyzeNativeIosProject,
|
|
5
5
|
findRootPbxproj,
|
|
6
6
|
hasNativeAndroidLayout
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-D5IVOO3T.js";
|
|
8
|
+
import "./chunk-XGVCTALD.js";
|
|
9
9
|
export {
|
|
10
10
|
analyzeNativeAndroidProject,
|
|
11
11
|
analyzeNativeIosProject,
|
package/package.json
CHANGED