Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions __tests__/command-executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,99 @@ describe("command-executor", () => {
expect.objectContaining({ description: "first release", rollout: 100 })
);
});

it("with --json emits the package JSON as the last stdout line and routes progress to stderr", async () => {
jest.spyOn(fs, "lstatSync").mockReturnValue({ isDirectory: () => false } as any);
mockSdkMethods.isAuthenticated.mockResolvedValue(true);
const pkg = {
label: "v3",
packageHash: "abc123hash",
size: 4242,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/v3",
manifestBlobUrl: "https://cdn.example.com/manifest/v3",
description: "first release",
releasedBy: "[email protected]",
releaseMethod: "Upload",
uploadTime: 1714867200000,
rollout: 100,
isMandatory: false,
isDisabled: false,
};
mockSdkMethods.release.mockResolvedValue(pkg);

await executor.release({
type: cli.CommandType.release,
appName: "MyApp",
deploymentName: "Production",
package: "./bundle.js",
appStoreVersion: "1.0.0",
description: "first release",
disabled: false,
mandatory: false,
rollout: 100,
noDuplicateReleaseError: false,
json: true,
});

const stdoutMessages = consoleLogSpy.mock.calls.map((c) => String(c[0]));
const lastStdout = stdoutMessages[stdoutMessages.length - 1];
const parsed = JSON.parse(lastStdout);
expect(parsed).toEqual({
label: "v3",
packageHash: "abc123hash",
size: 4242,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/v3",
manifestBlobUrl: "https://cdn.example.com/manifest/v3",
description: "first release",
releasedBy: "[email protected]",
releaseMethod: "Upload",
uploadTime: 1714867200000,
rollout: 100,
isMandatory: false,
isDisabled: false,
});
expect(stdoutMessages.some((m) => m.includes("Uploading release package"))).toBe(false);
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Uploading release package"));
});

it("without --json keeps progress on stdout and emits no JSON payload", async () => {
jest.spyOn(fs, "lstatSync").mockReturnValue({ isDirectory: () => false } as any);
mockSdkMethods.isAuthenticated.mockResolvedValue(true);
mockSdkMethods.release.mockResolvedValue({
label: "v3",
packageHash: "h",
size: 1,
appVersion: "1.0.0",
blobUrl: "u",
} as any);

await executor.release({
type: cli.CommandType.release,
appName: "MyApp",
deploymentName: "Production",
package: "./bundle.js",
appStoreVersion: "1.0.0",
description: "first release",
disabled: false,
mandatory: false,
rollout: 100,
noDuplicateReleaseError: false,
});

const stdoutMessages = consoleLogSpy.mock.calls.map((c) => String(c[0]));
expect(stdoutMessages.some((m) => m.includes("Uploading release package"))).toBe(true);
const jsonLines = stdoutMessages.filter((m) => {
try {
JSON.parse(m);
return true;
} catch {
return false;
}
});
expect(jsonLines.length).toBe(0);
});
});

describe("session / whoami", () => {
Expand Down
20 changes: 20 additions & 0 deletions __tests__/command-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,4 +898,24 @@ describe("command-parser", () => {
expect(cmd.ciMetadata).toBe(false);
});
});

describe("json output flag", () => {
it("leaves json undefined when --json is absent on release", () => {
const cmd = parseArgs(["release", "MyApp", "./bundle.js", "1.0.0"]);
expect(cmd.type).toBe(CommandType.release);
expect(cmd.json).toBeUndefined();
});

it("sets json true on release with --json", () => {
const cmd = parseArgs(["release", "MyApp", "./bundle.js", "1.0.0", "--json"]);
expect(cmd.type).toBe(CommandType.release);
expect(cmd.json).toBe(true);
});

it("sets json true on release-react with --json", () => {
const cmd = parseArgs(["release-react", "MyApp", "ios", "--json"]);
expect(cmd.type).toBe(CommandType.releaseReact);
expect(cmd.json).toBe(true);
});
});
});
69 changes: 69 additions & 0 deletions __tests__/utils/release-json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Aether. All rights reserved.

import { formatReleaseJson } from "../../script/utils/release-json";
import { Package } from "../../script/types";

describe("formatReleaseJson", () => {
it("emits every documented field when the package is fully populated", () => {
const pkg: Package = {
label: "v3",
packageHash: "9b8c7d6e5f4a3b2c",
size: 1234567,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/abc",
manifestBlobUrl: "https://cdn.example.com/manifest/abc",
description: "Bug fixes [ci=github sha=abc1234]",
releasedBy: "[email protected]",
releaseMethod: "Upload",
uploadTime: 1714867200000,
rollout: 100,
isMandatory: false,
isDisabled: false,
} as any;

expect(JSON.parse(formatReleaseJson(pkg))).toEqual({
label: "v3",
packageHash: "9b8c7d6e5f4a3b2c",
size: 1234567,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/abc",
manifestBlobUrl: "https://cdn.example.com/manifest/abc",
description: "Bug fixes [ci=github sha=abc1234]",
releasedBy: "[email protected]",
releaseMethod: "Upload",
uploadTime: 1714867200000,
rollout: 100,
isMandatory: false,
isDisabled: false,
});
});

it("drops fields that are undefined on the source package", () => {
const pkg: Package = {
label: "v3",
packageHash: "9b8c7d6e",
size: 100,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/abc",
} as any;

expect(JSON.parse(formatReleaseJson(pkg))).toEqual({
label: "v3",
packageHash: "9b8c7d6e",
size: 100,
appVersion: "1.0.0",
blobUrl: "https://cdn.example.com/blob/abc",
});
});

it("emits a single line of compact JSON", () => {
const pkg: Package = {
label: "v3",
packageHash: "x",
size: 1,
appVersion: "1.0.0",
blobUrl: "u",
} as any;
expect(formatReleaseJson(pkg).includes("\n")).toBe(false);
});
});
27 changes: 20 additions & 7 deletions script/command-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
import { getAndroidHermesEnabled, getiOSHermesEnabled, runHermesEmitBinaryCommand, isValidVersion } from "./react-native-utils";
import { fileDoesNotExistOrIsDirectory, isBinaryOrZip, fileExists } from "./utils/file-utils";
import { enrichDescriptionWithCiMetadata } from "./utils/ci-metadata";
import { formatReleaseJson } from "./utils/release-json";

const configFilePath: string = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".aether", "config.json");
const DEFAULT_AETHER_SERVER_URL = "https://api-staging.aetherpush.com";
Expand Down Expand Up @@ -71,6 +72,14 @@ export interface PackageWithMetrics {
}

export const log = (message: string | any): void => console.log(message);

function progressLog(command: cli.IReleaseBaseCommand, message: string): void {
if (command.json) {
console.error(message);
} else {
log(message);
}
}
export let sdk: AccountManager;
export const spawn = childProcess.spawn;
export const execSync = childProcess.execSync;
Expand Down Expand Up @@ -1372,11 +1381,12 @@ export const release = (command: cli.IReleaseCommand): Promise<void> => {
return sdk
.isAuthenticated(true)
.then((isAuth: boolean) => {
log("Uploading release package...");
progressLog(command, "Uploading release package...");
return sdk.release(command.appName, command.deploymentName, filePath, command.appStoreVersion, updateMetadata);
})
.then((): void => {
log(
.then((pkg: Package): void => {
progressLog(
command,
'Successfully released an update containing the "' +
command.package +
'" ' +
Expand All @@ -1387,6 +1397,9 @@ export const release = (command: cli.IReleaseCommand): Promise<void> => {
command.appName +
'" app.'
);
if (command.json) {
console.log(formatReleaseJson(pkg));
}
})
.catch((err: AetherError) => releaseErrorHandler(err, command));
};
Expand Down Expand Up @@ -1486,7 +1499,7 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
(platform === "ios" && (await getiOSHermesEnabled(command.podFile))); // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build

if (isHermesEnabled) {
log(chalk.cyan("\nRunning hermes compiler...\n"));
progressLog(command, chalk.cyan("\nRunning hermes compiler...\n"));
await runHermesEmitBinaryCommand(
bundleName,
outputFolder,
Expand All @@ -1498,14 +1511,14 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
})
.then(async () => {
if (command.privateKeyPath) {
log(chalk.cyan("\nSigning the bundle:\n"));
progressLog(command, chalk.cyan("\nSigning the bundle:\n"));
await sign(command.privateKeyPath, outputFolder);
} else {
console.log("private key was not provided");
progressLog(command, "private key was not provided");
}
})
.then(() => {
log(chalk.cyan("\nReleasing update contents to Aether:\n"));
progressLog(command, chalk.cyan("\nReleasing update contents to Aether:\n"));
return release(releaseCommand);
})
.then(() => {
Expand Down
14 changes: 14 additions & 0 deletions script/command-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,12 @@ yargs
description: "Percentage of users this release should be available to",
type: "string",
})
.option("json", {
demand: false,
description:
"Emit a JSON object describing the released package as the last line of stdout. Progress messages route to stderr.",
type: "boolean",
})
.check((argv: any, aliases: { [aliases: string]: string }): any => {
return checkValidReleaseOptions(argv);
});
Expand Down Expand Up @@ -973,6 +979,12 @@ yargs
"Name of build configuration which specifies the binary version you want to target this release at. For example, 'Debug' or 'Release' (iOS only)",
type: "string",
})
.option("json", {
demand: false,
description:
"Emit a JSON object describing the released package as the last line of stdout. Progress messages route to stderr.",
type: "boolean",
})
.check((argv: any, aliases: { [aliases: string]: string }): any => {
return checkValidReleaseOptions(argv);
});
Expand Down Expand Up @@ -1392,6 +1404,7 @@ export function createCommand(): cli.ICommand {
releaseCommand.mandatory = argv["mandatory"] as any;
releaseCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any;
releaseCommand.rollout = getRolloutValue(argv["rollout"] as any);
releaseCommand.json = argv["json"] as any;
}
break;

Expand Down Expand Up @@ -1426,6 +1439,7 @@ export function createCommand(): cli.ICommand {
releaseReactCommand.xcodeProjectFile = argv["xcodeProjectFile"] as any;
releaseReactCommand.xcodeTargetName = argv["xcodeTargetName"] as any;
releaseReactCommand.buildConfigurationName = argv["buildConfigurationName"] as any;
releaseReactCommand.json = argv["json"] as any;
}
break;

Expand Down
1 change: 1 addition & 0 deletions script/types/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export interface IReleaseBaseCommand extends ICommand, IPackageInfo {
deploymentName: string;
noDuplicateReleaseError?: boolean;
privateKeyPath?: string;
json?: boolean;
}

export interface IReleaseCommand extends IReleaseBaseCommand {
Expand Down
21 changes: 21 additions & 0 deletions script/utils/release-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Aether. All rights reserved.

import { Package } from "../types";

export function formatReleaseJson(pkg: Package): string {
return JSON.stringify({
label: pkg.label,
packageHash: pkg.packageHash,
size: pkg.size,
appVersion: pkg.appVersion,
blobUrl: pkg.blobUrl,
manifestBlobUrl: pkg.manifestBlobUrl,
description: pkg.description,
releasedBy: pkg.releasedBy,
releaseMethod: pkg.releaseMethod,
uploadTime: pkg.uploadTime,
rollout: pkg.rollout,
isMandatory: pkg.isMandatory,
isDisabled: pkg.isDisabled,
});
}