Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: firebase/firebase-tools
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.2.4
Choose a base ref
...
head repository: firebase/firebase-tools
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.3.0
Choose a head ref
  • 9 commits
  • 18 files changed
  • 5 contributors

Commits on Aug 20, 2019

  1. Copy the full SHA
    9906dea View commit details

Commits on Aug 22, 2019

  1. Deprecate firebase list command in favor of projects:list (#1586)

    * Deprecate `list` command
    
    * Deprecate getProjects() function in api.js
    
    * Update CHANGELOG and fix typos
    
    * Addressing comments
    
    * Remove TODO date
    
    * Remove TODO date
    TrCaM authored and bkendall committed Aug 22, 2019
    Copy the full SHA
    d348d54 View commit details
  2. Deprecate setup:web command (#1591)

    * Update output for apps:sdkconfig command
    
    * Deprecate setup:web command
    
    * Update CHANGELOG
    
    * Update src/commands/setup-web.js
    
    Co-Authored-By: Bryan Kendall <bkend@google.com>
    
    * Add deprecation date
    
    * Remove TODO date
    TrCaM authored and bkendall committed Aug 22, 2019
    Copy the full SHA
    6f3b4d7 View commit details
  3. Deprecate tools:migrate command (#1615)

    * Deprecate tools:migrate command
    
    * update TODO
    TrCaM authored and bkendall committed Aug 22, 2019
    Copy the full SHA
    dc6e7bd View commit details
  4. Copy the full SHA
    2066c22 View commit details

Commits on Aug 26, 2019

  1. Add app distribute command (#1621)

    Note: App Distribution is still a closed alpha, so you need to be whitelisted for this command to work.
    tonybaroneee authored Aug 26, 2019
    Copy the full SHA
    948978b View commit details
  2. Copy the full SHA
    4212a71 View commit details

Commits on Aug 27, 2019

  1. Copy the full SHA
    4f9e634 View commit details
  2. 7.3.0

    google-oss-bot committed Aug 27, 2019
    Copy the full SHA
    c687416 View commit details
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
* Update `portfinder` dependency to avoid breakage when installing `firebase-tools`.
* Mark `list` command as deprecated.
* Mark `setup:web` command as deprecated.
* Mark `tools:migrate` command as deprecated.
* Fix bug in Cloud Firestore emulator where committing a transaction with no writes would not release locks.
46 changes: 42 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-tools",
"version": "7.2.4",
"version": "7.3.0",
"description": "Command-Line Interface for Firebase",
"main": "./lib/index.js",
"bin": {
@@ -90,6 +90,7 @@
"marked": "^0.7.0",
"marked-terminal": "^3.3.0",
"minimatch": "^3.0.4",
"plist": "^3.0.1",
"open": "^6.3.0",
"ora": "^3.4.0",
"portfinder": "^1.0.23",
@@ -121,6 +122,7 @@
"@types/mocha": "^5.2.5",
"@types/nock": "^9.3.0",
"@types/node": "^10.12.0",
"@types/plist": "^3.0.1",
"@types/progress": "^2.0.3",
"@types/request": "^2.48.1",
"@types/semver": "^6.0.0",
16 changes: 16 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -99,6 +99,14 @@ var api = {
"https://logging.googleapis.com"
),
adminOrigin: utils.envOverride("FIREBASE_ADMIN_URL", "https://admin.firebase.com"),
appDistributionOrigin: utils.envOverride(
"FIREBASE_APP_DISTRIBUTION_URL",
"https://firebaseappdistribution.googleapis.com"
),
appDistributionUploadOrigin: utils.envOverride(
"FIREBASE_APP_DISTRIBUTION_UPLOAD_URL",
"https://appdistribution-uploads.crashlytics.com"
),
appengineOrigin: utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com"),
authOrigin: utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com"),
consoleOrigin: utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com"),
@@ -252,7 +260,15 @@ var api = {
return Promise.reject(err);
});
},

/**
* Deprecated. Call `listFirebaseProjects()` from `./management/project.ts` instead
* TODO: remove this function
*/
getProjects: function() {
logger.debug(
`[WARNING] ${new Error("getProjects() is deprecated - update the implementation").stack}`
);
return api
.request("GET", "/v1/projects", {
auth: true,
165 changes: 165 additions & 0 deletions src/appdistribution/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import * as _ from "lodash";
import * as api from "../api";
import * as utils from "../utils";
import { Distribution } from "./distribution";
import { FirebaseError } from "../error";

// tslint:disable-next-line:no-var-requires
const pkg = require("../../package.json");

/**
* Helper interface for an app that is provisioned with App Distribution
*/
export interface AppDistributionApp {
projectNumber: string;
appId: string;
platform: string;
bundleId: string;
contactEmail: string;
}

/**
* Proxies HTTPS requests to the App Distribution server backend.
*/
export class AppDistributionClient {
static MAX_POLLING_RETRIES = 30;
static POLLING_INTERVAL_MS = 1000;

constructor(private readonly appId: string) {}

async getApp(): Promise<AppDistributionApp> {
utils.logBullet("getting app details...");

const apiResponse = await api.request("GET", `/v1alpha/apps/${this.appId}`, {
origin: api.appDistributionOrigin,
auth: true,
});

return _.get(apiResponse, "body");
}

async getJwtToken(): Promise<string> {
const apiResponse = await api.request("GET", `/v1alpha/apps/${this.appId}/jwt`, {
auth: true,
origin: api.appDistributionOrigin,
});

return _.get(apiResponse, "body.token");
}

async uploadDistribution(token: string, distribution: Distribution): Promise<string> {
const apiResponse = await api.request("POST", "/spi/v1/jwt_distributions", {
origin: api.appDistributionUploadOrigin,
headers: {
Authorization: `Bearer ${token}`,
"X-APP-DISTRO-API-CLIENT-ID": pkg.name,
"X-APP-DISTRO-API-CLIENT-TYPE": distribution.platform(),
"X-APP-DISTRO-API-CLIENT-VERSION": pkg.version,
},
files: {
file: {
stream: distribution.readStream(),
size: distribution.fileSize(),
contentType: "multipart/form-data",
},
},
});

return _.get(apiResponse, "response.headers.etag");
}

async pollReleaseIdByHash(hash: string, retryCount = 0): Promise<string> {
try {
return await this.getReleaseIdByHash(hash);
} catch (err) {
if (retryCount >= AppDistributionClient.MAX_POLLING_RETRIES) {
throw new FirebaseError(`failed to find the uploaded release: ${err.message}`, { exit: 1 });
}

await new Promise((resolve) =>
setTimeout(resolve, AppDistributionClient.POLLING_INTERVAL_MS)
);

return this.pollReleaseIdByHash(hash, retryCount + 1);
}
}

async getReleaseIdByHash(hash: string): Promise<string> {
const apiResponse = await api.request(
"GET",
`/v1alpha/apps/${this.appId}/release_by_hash/${hash}`,
{
origin: api.appDistributionOrigin,
auth: true,
}
);

return _.get(apiResponse, "body.release.id");
}

async addReleaseNotes(releaseId: string, releaseNotes: string): Promise<void> {
if (!releaseNotes) {
utils.logWarning("no release notes specified, skipping");
return;
}

utils.logBullet("adding release notes...");

const data = {
releaseNotes: {
releaseNotes,
},
};

try {
await api.request("POST", `/v1alpha/apps/${this.appId}/releases/${releaseId}/notes`, {
origin: api.appDistributionOrigin,
auth: true,
data,
});
} catch (err) {
throw new FirebaseError(`failed to add release notes with ${err.message}`, { exit: 1 });
}

utils.logSuccess("added release notes successfully");
}

async enableAccess(
releaseId: string,
emails: string[] = [],
groupIds: string[] = []
): Promise<void> {
if (emails.length === 0 && groupIds.length === 0) {
utils.logWarning("no testers or groups specified, skipping");
return;
}

utils.logBullet("adding testers/groups...");

const data = {
emails,
groupIds,
};

try {
await api.request("POST", `/v1alpha/apps/${this.appId}/releases/${releaseId}/enable_access`, {
origin: api.appDistributionOrigin,
auth: true,
data,
});
} catch (err) {
let errorMessage = err.message;
if (_.has(err, "context.body.error")) {
const errorStatus = _.get(err, "context.body.error.status");
if (errorStatus === "FAILED_PRECONDITION") {
errorMessage = "invalid testers";
} else if (errorStatus === "INVALID_ARGUMENT") {
errorMessage = "invalid groups";
}
}
throw new FirebaseError(`failed to add testers/groups: ${errorMessage}`, { exit: 1 });
}

utils.logSuccess("added testers/groups successfully");
}
}
68 changes: 68 additions & 0 deletions src/appdistribution/distribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as fs from "fs-extra";
import { FirebaseError } from "../error";
import * as crypto from "crypto";

export enum DistributionFileType {
IPA = "ipa",
APK = "apk",
}

/**
* Object representing an APK or IPa file. Used for uploading app distributions.
*/
export class Distribution {
private readonly fileType: DistributionFileType;

constructor(private readonly path: string) {
if (!path) {
throw new FirebaseError("must specify a distribution file");
}

const distributionType = path.split(".").pop();
if (
distributionType !== DistributionFileType.IPA &&
distributionType !== DistributionFileType.APK
) {
throw new FirebaseError("unsupported distribution file format, should be .ipa or .apk");
}

if (!fs.existsSync(path)) {
throw new FirebaseError(
`File ${path} does not exist: verify that file points to a distribution`
);
}

this.path = path;
this.fileType = distributionType;
}

fileSize(): number {
return fs.statSync(this.path).size;
}

readStream(): fs.ReadStream {
return fs.createReadStream(this.path);
}

platform(): string {
switch (this.fileType) {
case DistributionFileType.IPA:
return "ios";
case DistributionFileType.APK:
return "android";
default:
throw new FirebaseError("Unsupported distribution file format, should be .ipa or .apk");
}
}

async releaseHash(): Promise<string> {
return new Promise<string>((resolve) => {
const hash = crypto.createHash("sha1");
const stream = this.readStream();
stream.on("data", (data) => hash.update(data));
stream.on("end", () => {
return resolve(hash.digest("hex"));
});
});
}
}
Loading