mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
A quick-and-dirty 'meteor publish' command.
It more or less works, but needs lots of cleanup.
This commit is contained in:
@@ -408,6 +408,84 @@ var fetchGalaxyOAuthInfo = function (galaxyName, timeout) {
|
||||
}
|
||||
};
|
||||
|
||||
// XXX De-dup with logInToGalaxy
|
||||
// XXX make args options
|
||||
var oauthFlow = function (conn, clientId, redirectUri,
|
||||
domain, sessionType) {
|
||||
var crypto = require('crypto');
|
||||
var credentialToken = crypto.randomBytes(16).toString('hex');
|
||||
var authCodeUrl = config.getOauthUrl() + "/authorize?" +
|
||||
querystring.stringify({
|
||||
state: credentialToken,
|
||||
response_type: "code",
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri
|
||||
});
|
||||
|
||||
// It's very important that we don't have request follow the
|
||||
// redirect for us, but instead issue the second request ourselves,
|
||||
// since request would pass our credentials along to the redirected
|
||||
// URL. See comments in http-helpers.js.
|
||||
try {
|
||||
var codeResult = httpHelpers.request({
|
||||
url: authCodeUrl,
|
||||
method: 'POST',
|
||||
strictSSL: true,
|
||||
useAuthHeader: true
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: 'no-account-server' };
|
||||
}
|
||||
var response = codeResult.response;
|
||||
if (response.statusCode !== 302 || ! response.headers.location) {
|
||||
return { error: 'access-denied' };
|
||||
}
|
||||
|
||||
if (url.parse(response.headers.location).hostname !==
|
||||
url.parse(redirectUri).hostname) {
|
||||
// If we didn't get an immediate redirect to the redirectUri then
|
||||
// presumably the oauth server is trying to interact with us (make
|
||||
// us log in, authorize the client, or something like that). We're
|
||||
// not a web browser so we can't participate in such things.
|
||||
return { error: 'access-denied' };
|
||||
}
|
||||
|
||||
try {
|
||||
var redirectResult = httpHelpers.request({
|
||||
url: response.headers.location,
|
||||
method: 'GET',
|
||||
strictSSL: true
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: 'no-package-server' };
|
||||
}
|
||||
|
||||
response = redirectResult.response;
|
||||
// 'access-denied' isn't exactly right because it's possible that the server
|
||||
// went down since our last request, but close enough.
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
return { error: 'access-denied' };
|
||||
}
|
||||
|
||||
// XXX tokenId???
|
||||
var loginResult = conn.apply('login', [{
|
||||
oauth: { credentialToken: credentialToken }
|
||||
}], { wait: true });
|
||||
|
||||
if (loginResult.token && loginResult.id) {
|
||||
var data = readSessionData();
|
||||
var session = getSession(data, domain);
|
||||
ensureSessionType(session, sessionType);
|
||||
session.token = loginResult.token;
|
||||
writeSessionData(data);
|
||||
return 0;
|
||||
} else {
|
||||
process.stderr.write('Login failed');
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Uses meteor accounts to log in to the specified galaxy. Returns an
|
||||
// object with keys `token` and `tokenId` if the login was
|
||||
// successful. If an error occurred, returns one of:
|
||||
@@ -949,3 +1027,5 @@ exports.loggedInUsername = function () {
|
||||
var data = readSessionData();
|
||||
return loggedIn(data) ? currentUsername(data) : false;
|
||||
};
|
||||
|
||||
exports.oauthFlow = oauthFlow;
|
||||
|
||||
@@ -14,6 +14,9 @@ var config = require('./config.js');
|
||||
var release = require('./release.js');
|
||||
var Future = require('fibers/future');
|
||||
var runLog = require('./run-log.js').runLog;
|
||||
var packageClient = require('./package-client.js');
|
||||
var utils = require('./utils.js');
|
||||
var httpHelpers = require('./http-helpers.js');
|
||||
|
||||
// Given a site name passed on the command line (eg, 'mysite'), return
|
||||
// a fully-qualified hostname ('mysite.meteor.com').
|
||||
@@ -1294,6 +1297,100 @@ main.registerCommand({
|
||||
});
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// publish a package
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
main.registerCommand({
|
||||
name: 'publish',
|
||||
minArgs: 0,
|
||||
maxArgs: 0,
|
||||
options: {
|
||||
versionString: { type: String, short: "v", required: true },
|
||||
// XXX A temporary option to create the package, until we sync
|
||||
// package metadata to the client.
|
||||
create: { type: Boolean }
|
||||
},
|
||||
requiresPackage: true
|
||||
}, function (options) {
|
||||
var pkg = release.current.library.get(path.basename(
|
||||
options.packageDir
|
||||
));
|
||||
|
||||
var name = pkg.name;
|
||||
var version = options.version;
|
||||
|
||||
var conn = packageClient.loggedInPackagesConnection();
|
||||
if (! conn) {
|
||||
process.stderr.write('Publish failed');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the package.
|
||||
// XXX First sync package metadata and check if it exists.
|
||||
if (options.create) {
|
||||
process.stdout.write('Creating package...\n');
|
||||
var packageId = conn.call('createPackage', {
|
||||
name: pkg.name
|
||||
});
|
||||
}
|
||||
|
||||
process.stdout.write('Creating package version...\n');
|
||||
var uploadInfo = conn.call('createPackageVersion', {
|
||||
packageName: pkg.name,
|
||||
version: options.versionString,
|
||||
description: pkg.metadata.summary
|
||||
});
|
||||
|
||||
process.stdout.write('Bundling source...\n');
|
||||
var tempTarball = path.join(options.packageDir,
|
||||
'source-' + utils.randomToken() + '.tgz');
|
||||
files.createTarball(options.packageDir, tempTarball, {
|
||||
ignoreDotFiles: true
|
||||
});
|
||||
|
||||
var size = fs.statSync(tempTarball).size;
|
||||
var crypto = require('crypto');
|
||||
var hash = crypto.createHash('sha256');
|
||||
hash.setEncoding('base64');
|
||||
var rs = fs.createReadStream(tempTarball);
|
||||
var fut = new Future();
|
||||
rs.on('end', function () {
|
||||
fut.return(hash.digest('base64'));
|
||||
});
|
||||
rs.pipe(hash, { end: false });
|
||||
var tarballHash = fut.wait();
|
||||
|
||||
rs.close();
|
||||
rs = fs.createReadStream(tempTarball);
|
||||
|
||||
process.stdout.write('Uploading source...\n');
|
||||
httpHelpers.request({
|
||||
method: 'PUT',
|
||||
url: uploadInfo.uploadUrl,
|
||||
headers: {
|
||||
'content-length': size,
|
||||
'content-type': 'application/octet-stream',
|
||||
'x-amz-acl': 'public-read'
|
||||
},
|
||||
bodyStream: rs
|
||||
});
|
||||
|
||||
// XXX Make sure this gets cleaned up even if we throw above
|
||||
fs.unlinkSync(tempTarball);
|
||||
|
||||
// XXX Upload build tarball
|
||||
|
||||
process.stdout.write('Publishing package version...\n');
|
||||
conn.call('publishPackageVersion', uploadInfo.uploadToken, tarballHash);
|
||||
conn.close();
|
||||
process.stdout.write('Published ' + pkg.name +
|
||||
', version ' + options.versionString);
|
||||
|
||||
process.stdout.write('\nDone!\n');
|
||||
return 0;
|
||||
});
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// dummy
|
||||
|
||||
@@ -127,6 +127,14 @@ _.extend(exports, {
|
||||
return addScheme(host);
|
||||
},
|
||||
|
||||
getPackageServerDomain: function () {
|
||||
if (isLocalUniverse()) {
|
||||
return localhostOffset(20);
|
||||
} else {
|
||||
return getUniverse();
|
||||
}
|
||||
},
|
||||
|
||||
// Return the domain name of the current Meteor Accounts server in
|
||||
// use. This is used as a key for storing your Meteor Accounts
|
||||
// login token.
|
||||
|
||||
@@ -85,6 +85,18 @@ files.findAppDir = function (filepath) {
|
||||
return findUpwards(isAppDir, filepath);
|
||||
};
|
||||
|
||||
files.findPackageDir = function (filepath) {
|
||||
var isPackageDir = function (filepath) {
|
||||
try {
|
||||
return fs.statSync(path.join(filepath, 'package.js')).isFile();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return findUpwards(isPackageDir, filepath);
|
||||
};
|
||||
|
||||
// create a .gitignore file in dirPath if one doesn't exist. add
|
||||
// 'entry' to the .gitignore on its own line at the bottom of the
|
||||
// file, if the exact line does not already exist in the file.
|
||||
@@ -404,17 +416,31 @@ files.extractTarGz = function (buffer, destPath) {
|
||||
// Tar-gzips a directory, returning a stream that can then be piped as
|
||||
// needed. The tar archive will contain a top-level directory named
|
||||
// after dirPath.
|
||||
files.createTarGzStream = function (dirPath) {
|
||||
// options:
|
||||
// - ignoreDotFiles: boolean
|
||||
files.createTarGzStream = function (dirPath, options) {
|
||||
var tar = require("tar");
|
||||
var fstream = require('fstream');
|
||||
var zlib = require("zlib");
|
||||
return fstream.Reader({ path: dirPath, type: 'Directory' }).pipe(
|
||||
tar.Pack()).pipe(zlib.createGzip());
|
||||
var filter;
|
||||
if (options.ignoreDotFiles) {
|
||||
filter = function () {
|
||||
return ! this.basename.match(/^\./);
|
||||
};
|
||||
}
|
||||
var reader = fstream.Reader({
|
||||
path: dirPath,
|
||||
type: 'Directory',
|
||||
filter: filter
|
||||
});
|
||||
return reader.pipe(tar.Pack()).pipe(zlib.createGzip());
|
||||
};
|
||||
|
||||
// Tar-gzips a directory into a tarball on disk, synchronously.
|
||||
// The tar archive will contain a top-level directory named after dirPath.
|
||||
files.createTarball = function (dirPath, tarball) {
|
||||
// options:
|
||||
// - ignoreDotFiles: boolean
|
||||
files.createTarball = function (dirPath, tarball, options) {
|
||||
var future = new Future;
|
||||
var out = fs.createWriteStream(tarball);
|
||||
out.on('error', function (err) {
|
||||
@@ -424,7 +450,7 @@ files.createTarball = function (dirPath, tarball) {
|
||||
future.return();
|
||||
});
|
||||
|
||||
files.createTarGzStream(dirPath).pipe(out);
|
||||
files.createTarGzStream(dirPath, options).pipe(out);
|
||||
future.wait();
|
||||
};
|
||||
|
||||
|
||||
@@ -387,3 +387,7 @@ Grant a permission on an official service
|
||||
Usage: meteor admin grant [XXX]
|
||||
|
||||
Not yet implemented
|
||||
|
||||
>>> publish
|
||||
Publish a package
|
||||
Usage: meteor publish --versionString <semver>
|
||||
|
||||
@@ -515,13 +515,16 @@ Fiber(function () {
|
||||
rawArgs.push(term);
|
||||
}
|
||||
|
||||
// Figure out if we're running in a directory that is part of a
|
||||
// Meteor application. Determine any additional directories to
|
||||
// Figure out if we're running in a directory that is part of a Meteor
|
||||
// application or package. Determine any additional directories to
|
||||
// search for packages.
|
||||
|
||||
var appDir = files.findAppDir();
|
||||
if (appDir)
|
||||
appDir = path.resolve(appDir);
|
||||
var packageDir = files.findPackageDir();
|
||||
if (packageDir)
|
||||
packageDir = path.resolve(packageDir);
|
||||
|
||||
var packageDirs = [];
|
||||
if (appDir)
|
||||
@@ -910,6 +913,22 @@ commandName + ": You're not in a Meteor project directory.\n" +
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Same check for commands that can only be run from a package dir.
|
||||
var requiresPackage = command.requiresPackage;
|
||||
if (typeof requiresPackage === "function") {
|
||||
requiresPackage = requiresPackage(options);
|
||||
}
|
||||
|
||||
if (packageDir) {
|
||||
options.packageDir = packageDir;
|
||||
}
|
||||
|
||||
if (requiresPackage && ! options.packageDir) {
|
||||
process.stderr.write(
|
||||
commandName + ": You're not in a Meteor package directory.\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (command.requiresRelease && ! release.current) {
|
||||
process.stderr.write(
|
||||
"You must specify a Meteor version with --release when you work with this\n" +
|
||||
|
||||
76
tools/package-client.js
Normal file
76
tools/package-client.js
Normal file
@@ -0,0 +1,76 @@
|
||||
var auth = require('./auth.js');
|
||||
var config = require('./config.js');
|
||||
var httpHelpers = require('./http-helpers.js');
|
||||
var release = require('./release.js');
|
||||
var Future = require('fibers/future');
|
||||
var _ = require('underscore');
|
||||
|
||||
var getLoadedPackages = _.once(function () {
|
||||
var unipackage = require('./unipackage.js');
|
||||
return unipackage.load({
|
||||
library: release.current.library,
|
||||
packages: [ 'meteor', 'livedata', 'minimongo', 'mongo-livedata' ],
|
||||
release: release.current.name
|
||||
});
|
||||
});
|
||||
|
||||
var openPackageServerConnection = function () {
|
||||
var DDP = getLoadedPackages().livedata.DDP;
|
||||
return DDP.connect(config.getPackageServerUrl(), {
|
||||
headers: { 'User-Agent': httpHelpers.getUserAgent() }
|
||||
});
|
||||
};
|
||||
|
||||
// XXX onReconnect
|
||||
exports.loggedInPackagesConnection = function () {
|
||||
|
||||
if (! auth.isLoggedIn()) {
|
||||
auth.doUsernamePasswordLogin({ retry: true });
|
||||
}
|
||||
|
||||
var conn = openPackageServerConnection();
|
||||
var serviceConfigurations = new (getLoadedPackages()['meteor'].
|
||||
Meteor.Collection)('meteor_accounts_loginServiceConfiguration', {
|
||||
connection: conn
|
||||
});
|
||||
var fut = new Future();
|
||||
var serviceConfigurationsSub = conn.subscribe(
|
||||
'meteor.loginServiceConfiguration',
|
||||
fut.resolver()
|
||||
);
|
||||
fut.wait();
|
||||
|
||||
var accountsConfiguration = serviceConfigurations.findOne({
|
||||
service: 'meteor-developer'
|
||||
});
|
||||
|
||||
if (! accountsConfiguration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var clientId = accountsConfiguration.clientId;
|
||||
var loginResult;
|
||||
|
||||
if (! auth.getSessionToken(config.getPackageServerDomain())) {
|
||||
// Since we passed retry: true, we shouldn't ever get to this point
|
||||
// unless we are now logged in with the accounts server.
|
||||
var redirectUri = config.getPackageServerUrl() +
|
||||
'/_oauth/meteor-developer?close';
|
||||
loginResult = auth.oauthFlow(conn, clientId, redirectUri,
|
||||
config.getPackageServerDomain(),
|
||||
'package-server');
|
||||
if (! loginResult) {
|
||||
conn.close();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
loginResult = conn.apply('login', [{
|
||||
resume: auth.getSessionToken(config.getPackageServerDomain())
|
||||
}], { wait: true });
|
||||
if (! loginResult || ! loginResult.token || ! loginResult.id) {
|
||||
conn.close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
};
|
||||
Reference in New Issue
Block a user