@lynxship/cli 0.1.0
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 +122 -0
- package/dist/android-build.d.ts +14 -0
- package/dist/android-build.d.ts.map +1 -0
- package/dist/android-build.js +201 -0
- package/dist/artifact-name.d.ts +3 -0
- package/dist/artifact-name.d.ts.map +1 -0
- package/dist/artifact-name.js +4 -0
- package/dist/autolink.d.ts +14 -0
- package/dist/autolink.d.ts.map +1 -0
- package/dist/autolink.js +144 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +62 -0
- package/dist/configure.d.ts +11 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +172 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1082 -0
- package/dist/ios-build.d.ts +13 -0
- package/dist/ios-build.d.ts.map +1 -0
- package/dist/ios-build.js +174 -0
- package/dist/ota-assets.d.ts +3 -0
- package/dist/ota-assets.d.ts.map +1 -0
- package/dist/ota-assets.js +48 -0
- package/dist/ota-doctor.d.ts +10 -0
- package/dist/ota-doctor.d.ts.map +1 -0
- package/dist/ota-doctor.js +53 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +12 -0
- package/dist/process-runner.d.ts +18 -0
- package/dist/process-runner.d.ts.map +1 -0
- package/dist/process-runner.js +97 -0
- package/dist/prompt.d.ts +3 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +58 -0
- package/dist/r2.d.ts +32 -0
- package/dist/r2.d.ts.map +1 -0
- package/dist/r2.js +135 -0
- package/dist/remote.d.ts +33 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +116 -0
- package/dist/runtime-fingerprint.d.ts +10 -0
- package/dist/runtime-fingerprint.d.ts.map +1 -0
- package/dist/runtime-fingerprint.js +238 -0
- package/dist/secure-store.d.ts +32 -0
- package/dist/secure-store.d.ts.map +1 -0
- package/dist/secure-store.js +263 -0
- package/dist/ui/colors.d.ts +24 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +64 -0
- package/dist/ui/components.d.ts +36 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/components.js +268 -0
- package/dist/ui/index.d.ts +24 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +68 -0
- package/dist/ui/logo.d.ts +3 -0
- package/dist/ui/logo.d.ts.map +1 -0
- package/dist/ui/logo.js +32 -0
- package/dist/ui/state.d.ts +5 -0
- package/dist/ui/state.d.ts.map +1 -0
- package/dist/ui/state.js +7 -0
- package/dist/ui/terminal.d.ts +12 -0
- package/dist/ui/terminal.d.ts.map +1 -0
- package/dist/ui/terminal.js +23 -0
- package/package.json +75 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { access, mkdir, readFile, unlink } from "node:fs/promises";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { prompt, secret } from "./prompt.js";
|
|
7
|
+
import { defaultEndpoint, verifyR2, writeR2Config, } from "./r2.js";
|
|
8
|
+
import { loadCredentials, saveCredentials, } from "./secure-store.js";
|
|
9
|
+
import { globalLynxShipDirectory } from "./paths.js";
|
|
10
|
+
export async function configureR2(root) {
|
|
11
|
+
const accountId = await prompt("Cloudflare account ID", process.env.CLOUDFLARE_ACCOUNT_ID);
|
|
12
|
+
const bucket = await prompt("R2 bucket name", "lynxship-artifacts");
|
|
13
|
+
const accessKeyId = await secret("R2 access key ID");
|
|
14
|
+
const secretAccessKey = await secret("R2 secret access key");
|
|
15
|
+
const expiresIn = Number(await prompt("Download link lifetime in seconds", "86400"));
|
|
16
|
+
const config = {
|
|
17
|
+
accountId,
|
|
18
|
+
bucket,
|
|
19
|
+
endpoint: defaultEndpoint(accountId),
|
|
20
|
+
expiresIn,
|
|
21
|
+
};
|
|
22
|
+
const credentials = { accessKeyId, secretAccessKey };
|
|
23
|
+
await verifyR2(config, credentials);
|
|
24
|
+
const existing = await loadCredentials(root);
|
|
25
|
+
await writeR2Config(root, config, { global: true });
|
|
26
|
+
await saveCredentials(root, { ...existing, r2: credentials }, { global: true });
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
export async function configureGooglePlay(root) {
|
|
30
|
+
const existing = await loadCredentials(root);
|
|
31
|
+
const serviceAccountPath = await prompt("Google service account JSON path");
|
|
32
|
+
const serviceAccountJson = await readFile(serviceAccountPath, "utf8");
|
|
33
|
+
const parsed = JSON.parse(serviceAccountJson);
|
|
34
|
+
if (!parsed.client_email || !parsed.private_key)
|
|
35
|
+
throw new Error("Google service account JSON must contain client_email and private_key");
|
|
36
|
+
const applicationId = await prompt("Android application ID", existing.googlePlay?.applicationId);
|
|
37
|
+
const track = await prompt("Google Play track", existing.googlePlay?.track ?? "internal");
|
|
38
|
+
const releaseStatus = await prompt("Release status", existing.googlePlay?.releaseStatus ?? "draft");
|
|
39
|
+
if (!["draft", "completed", "inProgress", "halted"].includes(releaseStatus))
|
|
40
|
+
throw new Error("Release status must be draft, completed, inProgress or halted");
|
|
41
|
+
await saveCredentials(root, {
|
|
42
|
+
...existing,
|
|
43
|
+
googlePlay: {
|
|
44
|
+
serviceAccountJson,
|
|
45
|
+
applicationId,
|
|
46
|
+
track,
|
|
47
|
+
releaseStatus: releaseStatus,
|
|
48
|
+
},
|
|
49
|
+
}, { global: true });
|
|
50
|
+
}
|
|
51
|
+
export async function configureAppStoreConnect(root) {
|
|
52
|
+
const existing = await loadCredentials(root);
|
|
53
|
+
const apiKeyId = await prompt("App Store Connect API key ID", existing.appStoreConnect?.apiKeyId);
|
|
54
|
+
const issuerId = await prompt("App Store Connect issuer ID", existing.appStoreConnect?.issuerId);
|
|
55
|
+
const bundleIdentifier = await prompt("iOS bundle identifier", existing.appStoreConnect?.bundleIdentifier);
|
|
56
|
+
const ascAppId = await prompt("App Store Connect app ID (optional)", existing.appStoreConnect?.ascAppId);
|
|
57
|
+
const privateKeyPath = await prompt("App Store Connect .p8 private key path");
|
|
58
|
+
const privateKey = await readFile(privateKeyPath, "utf8");
|
|
59
|
+
const transporterPath = await prompt("Transporter path (optional)", existing.appStoreConnect?.transporterPath);
|
|
60
|
+
await saveCredentials(root, {
|
|
61
|
+
...existing,
|
|
62
|
+
appStoreConnect: {
|
|
63
|
+
apiKeyId,
|
|
64
|
+
issuerId,
|
|
65
|
+
privateKey,
|
|
66
|
+
bundleIdentifier,
|
|
67
|
+
ascAppId: ascAppId || undefined,
|
|
68
|
+
transporterPath: transporterPath || undefined,
|
|
69
|
+
},
|
|
70
|
+
}, { global: true });
|
|
71
|
+
}
|
|
72
|
+
function keytoolPath(root) {
|
|
73
|
+
const javaHome = process.env.JAVA_HOME;
|
|
74
|
+
const executable = process.platform === "win32" ? "keytool.exe" : "keytool";
|
|
75
|
+
const candidates = [
|
|
76
|
+
javaHome ? join(javaHome, "bin", executable) : undefined,
|
|
77
|
+
join(root, "..", "..", ".tools", "jdk-17.0.20+8", "bin", executable),
|
|
78
|
+
].filter((value) => Boolean(value));
|
|
79
|
+
return candidates.find((value) => existsSync(value)) ?? executable;
|
|
80
|
+
}
|
|
81
|
+
async function generateAndroidKeystore(root, keyAlias) {
|
|
82
|
+
const directory = join(globalLynxShipDirectory(), "keys");
|
|
83
|
+
await mkdir(directory, { recursive: true });
|
|
84
|
+
const path = join(directory, "android-release.jks");
|
|
85
|
+
try {
|
|
86
|
+
await access(path);
|
|
87
|
+
throw new Error(`An Android keystore already exists at ${path}. Configure it explicitly or remove it before generating a new one.`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code !== "ENOENT")
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const password = randomBytes(48).toString("base64url");
|
|
94
|
+
const environment = {
|
|
95
|
+
...process.env,
|
|
96
|
+
LYNXSHIP_KEYTOOL_STOREPASS: password,
|
|
97
|
+
LYNXSHIP_KEYTOOL_KEYPASS: password,
|
|
98
|
+
};
|
|
99
|
+
const args = [
|
|
100
|
+
"-genkeypair",
|
|
101
|
+
"-keystore",
|
|
102
|
+
path,
|
|
103
|
+
"-storetype",
|
|
104
|
+
"JKS",
|
|
105
|
+
"-alias",
|
|
106
|
+
keyAlias,
|
|
107
|
+
"-keyalg",
|
|
108
|
+
"RSA",
|
|
109
|
+
"-keysize",
|
|
110
|
+
"2048",
|
|
111
|
+
"-validity",
|
|
112
|
+
"10000",
|
|
113
|
+
"-dname",
|
|
114
|
+
"CN=LynxShip Android, OU=Development, O=LynxShip, C=FR",
|
|
115
|
+
"-storepass:env",
|
|
116
|
+
"LYNXSHIP_KEYTOOL_STOREPASS",
|
|
117
|
+
"-keypass:env",
|
|
118
|
+
"LYNXSHIP_KEYTOOL_KEYPASS",
|
|
119
|
+
"-noprompt",
|
|
120
|
+
];
|
|
121
|
+
try {
|
|
122
|
+
await new Promise((resolve, reject) => {
|
|
123
|
+
const child = spawn(keytoolPath(root), args, {
|
|
124
|
+
env: environment,
|
|
125
|
+
stdio: "ignore",
|
|
126
|
+
windowsHide: true,
|
|
127
|
+
});
|
|
128
|
+
child.once("error", reject);
|
|
129
|
+
child.once("exit", (code) => {
|
|
130
|
+
if (code === 0)
|
|
131
|
+
resolve();
|
|
132
|
+
else
|
|
133
|
+
reject(new Error(`keytool failed with exit code ${code ?? "unknown"}`));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
await unlink(path).catch(() => undefined);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
return { path, password };
|
|
142
|
+
}
|
|
143
|
+
export async function configureAndroid(root) {
|
|
144
|
+
const existing = await loadCredentials(root);
|
|
145
|
+
const current = existing.android;
|
|
146
|
+
const keystorePath = await prompt("Android keystore path (leave empty to generate automatically)", current?.keystorePath ?? process.env.LYNXSHIP_KEYSTORE_PATH);
|
|
147
|
+
const keyAlias = await prompt("Android key alias", current?.keyAlias ?? process.env.LYNXSHIP_KEY_ALIAS ?? "lynxship-demo");
|
|
148
|
+
const generated = !keystorePath;
|
|
149
|
+
const generatedKeystore = generated
|
|
150
|
+
? await generateAndroidKeystore(root, keyAlias)
|
|
151
|
+
: undefined;
|
|
152
|
+
const resolvedKeystorePath = generatedKeystore?.path ?? keystorePath;
|
|
153
|
+
const keystorePassword = generatedKeystore?.password ?? (await secret("Android keystore password"));
|
|
154
|
+
const keyPassword = generatedKeystore?.password ?? (await secret("Android key password"));
|
|
155
|
+
try {
|
|
156
|
+
await access(resolvedKeystorePath);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new Error(`Android keystore was not found: ${resolvedKeystorePath}`);
|
|
160
|
+
}
|
|
161
|
+
const credentials = {
|
|
162
|
+
...existing,
|
|
163
|
+
android: {
|
|
164
|
+
keystorePath: resolvedKeystorePath,
|
|
165
|
+
keyAlias,
|
|
166
|
+
keystorePassword,
|
|
167
|
+
keyPassword,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
await saveCredentials(root, credentials, { global: true });
|
|
171
|
+
return { keystorePath: resolvedKeystorePath, generated };
|
|
172
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|