@lynxship/cli 0.1.3 → 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 CHANGED
@@ -97,6 +97,21 @@ Progress percentages are shown only when LynxShip has a real measurement. Long
97
97
  Rspeedy, Gradle and Xcode operations remain visible in the event journal until
98
98
  their completion checkpoint is known; no timer-based percentage is invented.
99
99
 
100
+ ### Project-safe Android signing
101
+
102
+ LynxShip does not require every project to contain a LynxShip-specific signing
103
+ helper. During a real Android build it creates a temporary Gradle init script,
104
+ passes it with Gradle's `--init-script` option, and removes it when the build
105
+ finishes. The script reads only the already configured machine credentials and
106
+ uses the Android Components `finalizeDsl` hook to configure the standard
107
+ `release` build type. The project's Gradle files are left unchanged.
108
+
109
+ This supports normal Android Gradle Plugin application projects using either
110
+ Groovy or Kotlin DSL. Projects with custom flavor tasks, non-standard signing
111
+ plugins, or no Android `release` build type must expose their signing contract
112
+ explicitly; LynxShip fails with a clear diagnostic rather than producing an
113
+ unverified artifact.
114
+
100
115
  ## Main commands
101
116
 
102
117
  ```text
@@ -1 +1 @@
1
- {"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAoID,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7D;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsCpE;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAyJnB"}
1
+ {"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AA0LD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7D;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsCpE;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAgKnB"}
@@ -1,6 +1,7 @@
1
- import { access, copyFile, cp, mkdir, readFile, readdir, } from "node:fs/promises";
1
+ import { access, chmod, copyFile, cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile, } from "node:fs/promises";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
4
5
  import { join } from "node:path";
5
6
  import { assert, sha256 } from "@lynxship/contracts";
6
7
  import { transitionBuild } from "@lynxship/build-orchestrator";
@@ -33,6 +34,56 @@ async function signingEnvironment(root) {
33
34
  assert(missing.length === 0, "BUILD_SIGNING_REQUIRED", `Signed Android builds require configuration. Missing: ${missing.join(", ")}. Run \`lynxship android configure\`.`);
34
35
  return { ...process.env, ...values };
35
36
  }
37
+ async function createSigningInitScript() {
38
+ const directory = await mkdtemp(join(tmpdir(), "lynxship-gradle-"));
39
+ const file = join(directory, "android-signing.init.gradle");
40
+ const script = `
41
+ def keystorePath = System.getenv("LYNXSHIP_KEYSTORE_PATH")
42
+ def keyAlias = System.getenv("LYNXSHIP_KEY_ALIAS")
43
+ def keystorePassword = System.getenv("LYNXSHIP_KEYSTORE_PASSWORD")
44
+ def keyPassword = System.getenv("LYNXSHIP_KEY_PASSWORD")
45
+
46
+ gradle.allprojects { project ->
47
+ project.plugins.withId("com.android.application") {
48
+ def configureSigning = { android ->
49
+ def signing = android.signingConfigs.findByName("release")
50
+ if (signing == null) {
51
+ signing = android.signingConfigs.create("lynxshipRelease")
52
+ }
53
+ signing.storeFile = project.file(keystorePath)
54
+ signing.storePassword = keystorePassword
55
+ signing.keyAlias = keyAlias
56
+ signing.keyPassword = keyPassword
57
+
58
+ def release = android.buildTypes.findByName("release")
59
+ if (release == null) {
60
+ throw new GradleException(
61
+ "LynxShip requires an Android release build type"
62
+ )
63
+ }
64
+ release.signingConfig = signing
65
+ project.logger.lifecycle(
66
+ "[LynxShip] Applied machine signing credentials to \${project.path}:release"
67
+ )
68
+ }
69
+
70
+ def androidComponents = project.extensions.findByName("androidComponents")
71
+ if (androidComponents != null) {
72
+ androidComponents.finalizeDsl { android ->
73
+ configureSigning(android)
74
+ }
75
+ } else {
76
+ project.afterEvaluate {
77
+ configureSigning(project.extensions.findByName("android"))
78
+ }
79
+ }
80
+ }
81
+ }
82
+ `;
83
+ await writeFile(file, script, "utf8");
84
+ await chmod(file, 0o600).catch(() => undefined);
85
+ return { directory, file };
86
+ }
36
87
  async function verifySignedArtifact(root, artifactPath, options) {
37
88
  if (artifactPath.endsWith(".apk")) {
38
89
  const apksigner = androidTool("apksigner");
@@ -161,6 +212,7 @@ export async function runRealAndroidBuild(job, options) {
161
212
  quiet: options.quiet ?? false,
162
213
  onOutput: options.onEvent,
163
214
  };
215
+ let signingInitDirectory;
164
216
  try {
165
217
  transitionBuild(job, "uploading_source", "local Android source prepared");
166
218
  job.logs.push({
@@ -189,7 +241,10 @@ export async function runRealAndroidBuild(job, options) {
189
241
  step("Android host synchronized", 40);
190
242
  transitionBuild(job, "building", `Gradle ${artifact.task}`);
191
243
  step(`Running real Gradle task ${artifact.task}…`);
192
- const gradleArgs = [artifact.task];
244
+ step("Applying temporary LynxShip Android signing adapter…");
245
+ const signingInitScript = await createSigningInitScript();
246
+ signingInitDirectory = signingInitScript.directory;
247
+ const gradleArgs = ["--init-script", signingInitScript.file, artifact.task];
193
248
  if (process.env.CI)
194
249
  gradleArgs.push("--stacktrace");
195
250
  await runProcess(wrapper, gradleArgs, {
@@ -258,4 +313,8 @@ export async function runRealAndroidBuild(job, options) {
258
313
  });
259
314
  throw error;
260
315
  }
316
+ finally {
317
+ if (signingInitDirectory)
318
+ await rm(signingInitDirectory, { recursive: true, force: true });
319
+ }
261
320
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxship/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -67,10 +67,10 @@
67
67
  "cli-progress": "3.12.0",
68
68
  "ora": "8.2.0",
69
69
  "qrcode-terminal": "0.12.0",
70
- "@lynxship/db": "0.1.0",
71
70
  "@lynxship/build-orchestrator": "0.1.0",
72
- "@lynxship/signing": "0.1.0",
71
+ "@lynxship/db": "0.1.0",
73
72
  "@lynxship/contracts": "0.1.0",
73
+ "@lynxship/signing": "0.1.0",
74
74
  "@lynxship/submit": "0.1.0"
75
75
  },
76
76
  "devDependencies": {