mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebd9761fb | ||
|
|
67c7554b80 | ||
|
|
d158cce9a3 | ||
|
|
5a73ec2ec8 | ||
|
|
a03da04578 | ||
|
|
d45c29087b | ||
|
|
00200c707e | ||
|
|
b228166900 | ||
|
|
d281859cf5 | ||
|
|
e00d217cfb | ||
|
|
00f40e84db | ||
|
|
dde5b3abe6 | ||
|
|
97bffde34c | ||
|
|
a6a1cc3bbb | ||
|
|
22e71497ec | ||
|
|
a32529b8fa | ||
|
|
732f053acd | ||
|
|
90fc709194 | ||
|
|
1d2b926962 | ||
|
|
fbc7b7f8ad | ||
|
|
4279f2f3f8 | ||
|
|
b8eec43b31 | ||
|
|
ce30d6c11f | ||
|
|
9f1e674f30 | ||
|
|
2f2761fa47 | ||
|
|
411f2be579 | ||
|
|
28a160fc1f | ||
|
|
2663d3e10d | ||
|
|
e55d7d6f1e | ||
|
|
092796f5f4 | ||
|
|
3f6d86c872 | ||
|
|
bdc9faa083 | ||
|
|
3e35f77da4 | ||
|
|
464f144a2b | ||
|
|
8f92396ed4 | ||
|
|
4e41a79404 | ||
|
|
d3c153e85a | ||
|
|
503b488153 | ||
|
|
005a65da79 | ||
|
|
36da7dcd5f | ||
|
|
0e5f734a1a | ||
|
|
3aeaf10e64 | ||
|
|
d70942190c | ||
|
|
ac400e5cb9 | ||
|
|
333d5fb5d2 | ||
|
|
f2c12ccbf5 | ||
|
|
79af1ea6f1 | ||
|
|
1ed7875b0b | ||
|
|
67a690e536 | ||
|
|
5f3bedd1e0 | ||
|
|
670a875792 | ||
|
|
faf82245dc | ||
|
|
066eeb5b5f | ||
|
|
77ec86b894 | ||
|
|
bf82dc7896 | ||
|
|
b4c5a30469 | ||
|
|
7ed6e2b909 | ||
|
|
8edd18be90 | ||
|
|
3bc9ae7f5b | ||
|
|
760c2327b4 | ||
|
|
fcb5069d24 | ||
|
|
221ab080a1 | ||
|
|
addf069f26 | ||
|
|
80221e52d9 | ||
|
|
3403b4a4de | ||
|
|
a1ac930f4d | ||
|
|
ed8396c6f5 | ||
|
|
a457d8823a | ||
|
|
2d44dcb8be | ||
|
|
85da731867 | ||
|
|
3af5fdb831 | ||
|
|
f1b197acbb | ||
|
|
d483c81887 | ||
|
|
3ef980ae2b | ||
|
|
36e68b46df | ||
|
|
f611dfdb5c | ||
|
|
148d0d8d29 | ||
|
|
79a1382126 | ||
|
|
b0d5ba1996 | ||
|
|
088dc70dae | ||
|
|
ad686cca68 | ||
|
|
48a4b58cc1 | ||
|
|
c1ef824e4c | ||
|
|
a2eb532720 | ||
|
|
0cb53ceb9d | ||
|
|
afe4e610ae | ||
|
|
74d90fbb33 | ||
|
|
f3bd8f6133 | ||
|
|
863c511fbf | ||
|
|
03d8689ded | ||
|
|
ff2bc1d705 | ||
|
|
a1df8befde | ||
|
|
75909685f7 | ||
|
|
2e0a015168 | ||
|
|
740b2797c5 | ||
|
|
5f372f7ecc | ||
|
|
e4f4a1d9f9 | ||
|
|
bdc255fa9e | ||
|
|
e8e542b3e8 | ||
|
|
dd89aa6c77 | ||
|
|
79caff69f5 | ||
|
|
67b10135b6 | ||
|
|
6a86831b7c | ||
|
|
dffd17ab70 | ||
|
|
cf98934dd9 | ||
|
|
2fff138740 |
7
.env.example
Normal file
7
.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
# These env vars are only necessary for creating Electron releases.
|
||||
# See docs/development/releasing.md
|
||||
|
||||
APPVEYOR_TOKEN=
|
||||
CIRCLE_TOKEN=
|
||||
ELECTRON_GITHUB_TOKEN=
|
||||
VSTS_TOKEN=
|
||||
@@ -2,7 +2,9 @@ build_cloud: electron-16
|
||||
image: electron-16-vs2015
|
||||
build_script:
|
||||
- ps: >-
|
||||
if(($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME -split "/")[0] -eq ($env:APPVEYOR_REPO_NAME -split "/")[0]) {
|
||||
if($env:SKIP_GYP_BUILD -eq "true") {
|
||||
Write-warning "Skipping debug build for older branch"; Exit-AppveyorBuild
|
||||
} elseif(($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME -split "/")[0] -eq ($env:APPVEYOR_REPO_NAME -split "/")[0]) {
|
||||
Write-warning "Skipping PR build for branch"; Exit-AppveyorBuild
|
||||
} else {
|
||||
Add-Path "$env:ProgramFiles (x86)\Windows Kits\10\Debuggers\x64"
|
||||
@@ -55,4 +57,13 @@ artifacts:
|
||||
- path: test-results.xml
|
||||
name: test-results.xml
|
||||
deploy_script:
|
||||
- ps: "if (Test-Path Env:\\ELECTRON_RELEASE) {\n if (Test-Path Env:\\RUN_RELEASE_BUILD) {\n Write-Output \"Uploading Electron release distribution to s3\"\n & python script\\upload.py --upload_to_s3\n } else {\n Write-Output \"Uploading Electron release distribution to github releases\"\n & python script\\upload.py\n if (Test-Path Env:\\AUTO_RELEASE) {\n node script\\release.js --validateRelease --automaticRelease\n if ($? -eq 'True') {\n echo 'Release is ready to go; now running release'\n node script\\release.js --automaticRelease\n if ($? -eq 'True') { \n echo 'Release successful, now publishing to npm' \n $npmfile = \"$HOME\\.npmrc\"\n \"//registry.npmjs.org/:_authToken=$env:ELECTRON_NPM_TOKEN\" > $npmfile\n npm run publish-to-npm\n }\n } else {\n echo 'Release is not complete, skipping publish for now.'\n }\n }\n }\n} else {\n Write-Output \"Skipping upload distribution because build is not for release\"\n}"
|
||||
- ps: >-
|
||||
if (Test-Path Env:\ELECTRON_RELEASE) {
|
||||
if (Test-Path Env:\RUN_RELEASE_BUILD) {
|
||||
Write-Output "Uploading Electron release distribution to s3"
|
||||
& python script\upload.py --upload_to_s3
|
||||
} else {
|
||||
Write-Output "Uploading Electron release distribution to github releases"
|
||||
& python script\upload.py
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,12 @@
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace {
|
||||
|
||||
static scoped_nsobject<NSMenu> applicationMenu_;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
@@ -135,7 +141,18 @@ void Menu::SetApplicationMenu(Menu* base_menu) {
|
||||
base::scoped_nsobject<AtomMenuController> menu_controller(
|
||||
[[AtomMenuController alloc] initWithModel:menu->model_.get()
|
||||
useDefaultAccelerator:YES]);
|
||||
[NSApp setMainMenu:[menu_controller menu]];
|
||||
|
||||
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
|
||||
[currentRunLoop cancelPerformSelector:@selector(setMainMenu:)
|
||||
target:NSApp
|
||||
argument:applicationMenu_];
|
||||
applicationMenu_.reset([[menu_controller menu] retain]);
|
||||
[[NSRunLoop currentRunLoop]
|
||||
performSelector:@selector(setMainMenu:)
|
||||
target:NSApp
|
||||
argument:applicationMenu_
|
||||
order:0
|
||||
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
|
||||
|
||||
// Ensure the menu_controller_ is destroyed after main menu is set.
|
||||
menu_controller.swap(menu->menu_controller_);
|
||||
|
||||
@@ -206,7 +206,7 @@ void Tray::PopUpContextMenu(mate::Arguments* args) {
|
||||
|
||||
void Tray::SetContextMenu(v8::Isolate* isolate, mate::Handle<Menu> menu) {
|
||||
menu_.Reset(isolate, menu.ToV8());
|
||||
tray_icon_->SetContextMenu(menu->model());
|
||||
tray_icon_->SetContextMenu(menu.IsEmpty() ? nullptr : menu->model());
|
||||
}
|
||||
|
||||
gfx::Rect Tray::GetBounds() {
|
||||
|
||||
@@ -310,7 +310,8 @@ WebContents::WebContents(v8::Isolate* isolate,
|
||||
type_(type),
|
||||
request_id_(0),
|
||||
background_throttling_(true),
|
||||
enable_devtools_(true) {
|
||||
enable_devtools_(true),
|
||||
is_dom_ready_(false) {
|
||||
const mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate);
|
||||
if (type == REMOTE) {
|
||||
web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent());
|
||||
@@ -799,8 +800,10 @@ void WebContents::DidChangeThemeColor(SkColor theme_color) {
|
||||
|
||||
void WebContents::DocumentLoadedInFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
if (!render_frame_host->GetParent())
|
||||
if (!render_frame_host->GetParent()) {
|
||||
is_dom_ready_ = true;
|
||||
Emit("dom-ready");
|
||||
}
|
||||
}
|
||||
|
||||
void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host,
|
||||
@@ -822,6 +825,7 @@ void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host,
|
||||
}
|
||||
|
||||
void WebContents::DidStartLoading() {
|
||||
is_dom_ready_ = false;
|
||||
Emit("did-start-loading");
|
||||
}
|
||||
|
||||
@@ -1343,6 +1347,10 @@ bool WebContents::IsAudioMuted() {
|
||||
return web_contents()->IsAudioMuted();
|
||||
}
|
||||
|
||||
bool WebContents::IsDOMReady() const {
|
||||
return is_dom_ready_;
|
||||
}
|
||||
|
||||
void WebContents::Print(mate::Arguments* args) {
|
||||
PrintSettings settings = { false, false, base::string16() };
|
||||
if (args->Length() >= 1 && !args->GetNext(&settings)) {
|
||||
@@ -1934,6 +1942,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
|
||||
&WebContents::SetIgnoreMenuShortcuts)
|
||||
.SetMethod("setAudioMuted", &WebContents::SetAudioMuted)
|
||||
.SetMethod("isAudioMuted", &WebContents::IsAudioMuted)
|
||||
.SetMethod("isDomReady", &WebContents::IsDOMReady)
|
||||
.SetMethod("undo", &WebContents::Undo)
|
||||
.SetMethod("redo", &WebContents::Redo)
|
||||
.SetMethod("cut", &WebContents::Cut)
|
||||
|
||||
@@ -125,6 +125,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||
void SetIgnoreMenuShortcuts(bool ignore);
|
||||
void SetAudioMuted(bool muted);
|
||||
bool IsAudioMuted();
|
||||
bool IsDOMReady() const;
|
||||
void Print(mate::Arguments* args);
|
||||
std::vector<printing::PrinterBasicInfo> GetPrinterList();
|
||||
void SetEmbedder(const WebContents* embedder);
|
||||
@@ -430,6 +431,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||
// Whether to enable devtools.
|
||||
bool enable_devtools_;
|
||||
|
||||
// Whether page's document is ready.
|
||||
bool is_dom_ready_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WebContents);
|
||||
};
|
||||
|
||||
|
||||
@@ -450,9 +450,11 @@ gfx::Rect Window::GetContentBounds() {
|
||||
}
|
||||
|
||||
void Window::SetSize(int width, int height, mate::Arguments* args) {
|
||||
gfx::Size size = window_->GetMinimumSize();
|
||||
size.SetToMax(gfx::Size(width, height));
|
||||
bool animate = false;
|
||||
args->GetNext(&animate);
|
||||
window_->SetSize(gfx::Size(width, height), animate);
|
||||
window_->SetSize(size, animate);
|
||||
}
|
||||
|
||||
std::vector<int> Window::GetSize() {
|
||||
|
||||
@@ -151,6 +151,14 @@ void AtomBrowserMainParts::PostEarlyInitialization() {
|
||||
|
||||
// Wrap the uv loop with global env.
|
||||
node_bindings_->set_uv_env(env);
|
||||
|
||||
// We already initialized the feature list in
|
||||
// brightray::BrowserMainParts::PreEarlyInitialization(), but
|
||||
// the user JS script would not have had a chance to alter the command-line
|
||||
// switches at that point. Lets reinitialize it here to pick up the
|
||||
// command-line changes.
|
||||
base::FeatureList::ClearInstanceForTesting();
|
||||
brightray::BrowserMainParts::InitializeFeatureList();
|
||||
}
|
||||
|
||||
int AtomBrowserMainParts::PreCreateThreads() {
|
||||
|
||||
@@ -215,6 +215,65 @@ Browser::LoginItemSettings Browser::GetLoginItemSettings(
|
||||
return settings;
|
||||
}
|
||||
|
||||
// copied from GetLoginItemForApp in base/mac/mac_util.mm
|
||||
LSSharedFileListItemRef GetLoginItemForApp() {
|
||||
base::ScopedCFTypeRef<LSSharedFileListRef> login_items(
|
||||
LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL));
|
||||
if (!login_items.get()) {
|
||||
LOG(ERROR) << "Couldn't get a Login Items list.";
|
||||
return NULL;
|
||||
}
|
||||
base::scoped_nsobject<NSArray> login_items_array(
|
||||
base::mac::CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL)));
|
||||
NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]];
|
||||
for (NSUInteger i = 0; i < [login_items_array count]; ++i) {
|
||||
LSSharedFileListItemRef item =
|
||||
reinterpret_cast<LSSharedFileListItemRef>(login_items_array[i]);
|
||||
CFURLRef item_url_ref = NULL;
|
||||
if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr &&
|
||||
item_url_ref) {
|
||||
base::ScopedCFTypeRef<CFURLRef> item_url(item_url_ref);
|
||||
if (CFEqual(item_url, url)) {
|
||||
CFRetain(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void RemoveFromLoginItems() {
|
||||
base::ScopedCFTypeRef<LSSharedFileListRef> list(
|
||||
LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL));
|
||||
if (!list) {
|
||||
LOG(ERROR) << "Unable to access shared file list";
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetLoginItemForApp() != NULL) {
|
||||
base::scoped_nsobject<NSArray> login_items_array(
|
||||
base::mac::CFToNSCast(LSSharedFileListCopySnapshot(list, NULL)));
|
||||
|
||||
if (!login_items_array) {
|
||||
LOG(ERROR) << "No items in list of auto-loaded apps";
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger i = 0; i < [login_items_array count]; ++i) {
|
||||
LSSharedFileListItemRef item =
|
||||
reinterpret_cast<LSSharedFileListItemRef>(login_items_array[i]);
|
||||
CFURLRef url_ref = NULL;
|
||||
if (LSSharedFileListItemResolve(item, 0, &url_ref, NULL) == noErr &&
|
||||
item) {
|
||||
base::ScopedCFTypeRef<CFURLRef> url(url_ref);
|
||||
if ([[base::mac::CFToNSCast(url.get()) path]
|
||||
hasPrefix:[[NSBundle mainBundle] bundlePath]])
|
||||
LSSharedFileListItemRemove(list, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
#if defined(MAS_BUILD)
|
||||
platform_util::SetLoginItemEnabled(settings.open_at_login);
|
||||
@@ -222,7 +281,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
if (settings.open_at_login)
|
||||
base::mac::AddToLoginItems(settings.open_as_hidden);
|
||||
else
|
||||
base::mac::RemoveFromLoginItems();
|
||||
RemoveFromLoginItems();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <unistd.h>
|
||||
#include <uv.h>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "device/bluetooth/dbus/dbus_thread_manager_linux.h"
|
||||
@@ -23,7 +24,7 @@ std::string get_executable_basename() {
|
||||
if (!uv_exepath(buf, &buf_size)) {
|
||||
rv = strrchr(static_cast<const char*>(buf), '/') + 1;
|
||||
}
|
||||
return std::move(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -32,39 +33,37 @@ namespace atom {
|
||||
|
||||
PowerObserverLinux::PowerObserverLinux()
|
||||
: lock_owner_name_(get_executable_basename()), weak_ptr_factory_(this) {
|
||||
auto dbus_thread_manager = bluez::DBusThreadManagerLinux::Get();
|
||||
if (dbus_thread_manager) {
|
||||
bus_ = dbus_thread_manager->GetSystemBus();
|
||||
if (bus_) {
|
||||
logind_ = bus_->GetObjectProxy(kLogindServiceName,
|
||||
dbus::ObjectPath(kLogindObjectPath));
|
||||
logind_->WaitForServiceToBeAvailable(
|
||||
base::Bind(&PowerObserverLinux::OnLoginServiceAvailable,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
LOG(WARNING) << "Failed to get system bus connection";
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "DBusThreadManagerLinux instance isn't available";
|
||||
auto* bus = bluez::DBusThreadManagerLinux::Get()->GetSystemBus();
|
||||
if (!bus) {
|
||||
LOG(WARNING) << "Failed to get system bus connection";
|
||||
return;
|
||||
}
|
||||
|
||||
// set up the logind proxy
|
||||
|
||||
const auto weakThis = weak_ptr_factory_.GetWeakPtr();
|
||||
|
||||
logind_ = bus->GetObjectProxy(kLogindServiceName,
|
||||
dbus::ObjectPath(kLogindObjectPath));
|
||||
logind_->ConnectToSignal(
|
||||
kLogindManagerInterface, "PrepareForShutdown",
|
||||
base::BindRepeating(&PowerObserverLinux::OnPrepareForShutdown, weakThis),
|
||||
base::BindRepeating(&PowerObserverLinux::OnSignalConnected, weakThis));
|
||||
logind_->ConnectToSignal(
|
||||
kLogindManagerInterface, "PrepareForSleep",
|
||||
base::BindRepeating(&PowerObserverLinux::OnPrepareForSleep, weakThis),
|
||||
base::BindRepeating(&PowerObserverLinux::OnSignalConnected, weakThis));
|
||||
logind_->WaitForServiceToBeAvailable(base::BindRepeating(
|
||||
&PowerObserverLinux::OnLoginServiceAvailable, weakThis));
|
||||
}
|
||||
|
||||
PowerObserverLinux::~PowerObserverLinux() = default;
|
||||
|
||||
void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) {
|
||||
if (!service_available) {
|
||||
LOG(WARNING) << kLogindServiceName << " not available";
|
||||
return;
|
||||
}
|
||||
// Connect to PrepareForShutdown/PrepareForSleep signals
|
||||
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForShutdown",
|
||||
base::Bind(&PowerObserverLinux::OnPrepareForShutdown,
|
||||
weak_ptr_factory_.GetWeakPtr()),
|
||||
base::Bind(&PowerObserverLinux::OnSignalConnected,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForSleep",
|
||||
base::Bind(&PowerObserverLinux::OnPrepareForSleep,
|
||||
weak_ptr_factory_.GetWeakPtr()),
|
||||
base::Bind(&PowerObserverLinux::OnSignalConnected,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
// Take sleep inhibit lock
|
||||
BlockSleep();
|
||||
}
|
||||
@@ -120,8 +119,10 @@ void PowerObserverLinux::SetShutdownHandler(base::Callback<bool()> handler) {
|
||||
|
||||
void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd,
|
||||
dbus::Response* response) {
|
||||
dbus::MessageReader reader(response);
|
||||
reader.PopFileDescriptor(scoped_fd);
|
||||
if (response != nullptr) {
|
||||
dbus::MessageReader reader(response);
|
||||
reader.PopFileDescriptor(scoped_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) {
|
||||
@@ -156,7 +157,7 @@ void PowerObserverLinux::OnPrepareForShutdown(dbus::Signal* signal) {
|
||||
}
|
||||
}
|
||||
|
||||
void PowerObserverLinux::OnSignalConnected(const std::string& interface,
|
||||
void PowerObserverLinux::OnSignalConnected(const std::string& /*interface*/,
|
||||
const std::string& signal,
|
||||
bool success) {
|
||||
LOG_IF(WARNING, !success) << "Failed to connect to " << signal;
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace atom {
|
||||
class PowerObserverLinux : public base::PowerObserver {
|
||||
public:
|
||||
PowerObserverLinux();
|
||||
~PowerObserverLinux() override;
|
||||
|
||||
protected:
|
||||
void BlockSleep();
|
||||
@@ -39,7 +40,6 @@ class PowerObserverLinux : public base::PowerObserver {
|
||||
|
||||
base::Callback<bool()> should_shutdown_;
|
||||
|
||||
scoped_refptr<dbus::Bus> bus_;
|
||||
scoped_refptr<dbus::ObjectProxy> logind_;
|
||||
std::string lock_owner_name_;
|
||||
base::ScopedFD sleep_lock_;
|
||||
|
||||
@@ -213,6 +213,7 @@ class NativeWindowMac : public NativeWindow,
|
||||
bool was_maximizable_;
|
||||
bool was_movable_;
|
||||
NSRect original_frame_;
|
||||
NSInteger original_level_;
|
||||
NSUInteger simple_fullscreen_mask_;
|
||||
|
||||
base::scoped_nsobject<NSColor> background_color_before_vibrancy_;
|
||||
|
||||
@@ -380,6 +380,7 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||
|
||||
// Set window style to hide the toolbar, otherwise the toolbar will show in
|
||||
// fullscreen mode.
|
||||
[window setTitlebarAppearsTransparent:NO];
|
||||
shell_->SetStyleMask(true, NSFullSizeContentViewWindowMask);
|
||||
}
|
||||
}
|
||||
@@ -397,6 +398,7 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||
// Turn off the style for toolbar.
|
||||
if (base::mac::IsAtLeastOS10_10() &&
|
||||
shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) {
|
||||
[window setTitlebarAppearsTransparent:YES];
|
||||
shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask);
|
||||
}
|
||||
}
|
||||
@@ -728,10 +730,20 @@ enum {
|
||||
|
||||
// Custom window button methods
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender { return YES; }
|
||||
|
||||
- (void)performClose:(id)sender {
|
||||
if (shell_->title_bar_style() == atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER)
|
||||
if (shell_->title_bar_style() ==
|
||||
atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER) {
|
||||
[[self delegate] windowShouldClose:self];
|
||||
else
|
||||
} else if (shell_->IsSimpleFullScreen()) {
|
||||
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if(![[self delegate] windowShouldClose:self]) return;
|
||||
} else if([self respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if(![self windowShouldClose:self]) return;
|
||||
}
|
||||
[self close];
|
||||
} else
|
||||
[super performClose:sender];
|
||||
}
|
||||
|
||||
@@ -1045,6 +1057,10 @@ NativeWindowMac::NativeWindowMac(
|
||||
|
||||
RegisterInputEventObserver(
|
||||
web_contents->GetWebContents()->GetRenderViewHost());
|
||||
|
||||
original_frame_ = [window_ frame];
|
||||
original_level_ = [window_ level];
|
||||
|
||||
}
|
||||
|
||||
NativeWindowMac::~NativeWindowMac() {
|
||||
@@ -1139,12 +1155,12 @@ bool NativeWindowMac::IsEnabled() {
|
||||
|
||||
void NativeWindowMac::SetEnabled(bool enable) {
|
||||
if (enable) {
|
||||
[window_ endSheet: [window_ attachedSheet]];
|
||||
} else {
|
||||
[window_ beginSheet: window_ completionHandler:^(NSModalResponse returnCode) {
|
||||
NSLog(@"modal enabled");
|
||||
return;
|
||||
}];
|
||||
} else {
|
||||
[window_ endSheet: [window_ attachedSheet]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1411,8 +1427,9 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
|
||||
if (simple_fullscreen && !is_simple_fullscreen_) {
|
||||
is_simple_fullscreen_ = true;
|
||||
|
||||
// Take note of the current window size
|
||||
// Take note of the current window size and level
|
||||
original_frame_ = [window frame];
|
||||
original_level_ = [window level];
|
||||
|
||||
simple_fullscreen_options_ = [NSApp currentSystemPresentationOptions];
|
||||
simple_fullscreen_mask_ = [window styleMask];
|
||||
@@ -1428,6 +1445,13 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
|
||||
|
||||
NSRect fullscreenFrame = [window.screen frame];
|
||||
|
||||
// If our app has dock hidden, set the window level higher so another app's
|
||||
// menu bar doesn't appear on top of our fullscreen app.
|
||||
if ([[NSRunningApplication currentApplication] activationPolicy] !=
|
||||
NSApplicationActivationPolicyRegular) {
|
||||
window.level = NSPopUpMenuWindowLevel;
|
||||
}
|
||||
|
||||
if ( !fullscreen_window_title() ) {
|
||||
// Hide the titlebar
|
||||
SetStyleMask(false, NSTitledWindowMask);
|
||||
@@ -1462,6 +1486,7 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
|
||||
}
|
||||
|
||||
[window setFrame:original_frame_ display: YES animate: YES];
|
||||
window.level = original_level_;
|
||||
|
||||
[NSApp setPresentationOptions:simple_fullscreen_options_];
|
||||
|
||||
|
||||
@@ -608,6 +608,7 @@ void NativeWindowViews::SetResizable(bool resizable) {
|
||||
// both the minimum and maximum size to the window size to achieve it.
|
||||
if (resizable) {
|
||||
SetContentSizeConstraints(old_size_constraints_);
|
||||
SetMaximizable(maximizable_);
|
||||
} else {
|
||||
old_size_constraints_ = GetContentSizeConstraints();
|
||||
resizable_ = false;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "atom/browser/node_debugger.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "libplatform/libplatform.h"
|
||||
@@ -28,10 +30,15 @@ void NodeDebugger::Start(node::NodePlatform* platform) {
|
||||
node::DebugOptions options;
|
||||
for (auto& arg : base::CommandLine::ForCurrentProcess()->argv()) {
|
||||
#if defined(OS_WIN)
|
||||
options.ParseOption("Electron", base::UTF16ToUTF8(arg));
|
||||
const std::string nice_arg = base::UTF16ToUTF8(arg);
|
||||
#else
|
||||
options.ParseOption("Electron", arg);
|
||||
const std::string& nice_arg = arg;
|
||||
#endif
|
||||
// Stop handling arguments after a "--" to be consistent with Chromium
|
||||
if (nice_arg == "--")
|
||||
break;
|
||||
|
||||
options.ParseOption("Electron", nice_arg);
|
||||
}
|
||||
|
||||
if (options.inspector_enabled()) {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>electron.icns</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.6</string>
|
||||
<string>2.0.13</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.6</string>
|
||||
<string>2.0.13</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -56,8 +56,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,0,6,0
|
||||
PRODUCTVERSION 2,0,6,0
|
||||
FILEVERSION 2,0,13,0
|
||||
PRODUCTVERSION 2,0,13,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -74,12 +74,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "GitHub, Inc."
|
||||
VALUE "FileDescription", "Electron"
|
||||
VALUE "FileVersion", "2.0.6"
|
||||
VALUE "FileVersion", "2.0.13"
|
||||
VALUE "InternalName", "electron.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "electron.exe"
|
||||
VALUE "ProductName", "Electron"
|
||||
VALUE "ProductVersion", "2.0.6"
|
||||
VALUE "ProductVersion", "2.0.13"
|
||||
VALUE "SquirrelAwareVersion", "1"
|
||||
END
|
||||
END
|
||||
|
||||
@@ -91,7 +91,9 @@ bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
if (base::ContainsKey(*table, accelerator)) {
|
||||
const accelerator_util::MenuItem& item = (*table)[accelerator];
|
||||
if (item.model->IsEnabledAt(item.position)) {
|
||||
item.model->ActivatedAt(item.position);
|
||||
const auto event_flags =
|
||||
accelerator.MaskOutKeyEventFlags(accelerator.modifiers());
|
||||
item.model->ActivatedAt(item.position, event_flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ class GtkMessageBox : public NativeWindowObserver {
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const std::string& checkbox_label,
|
||||
bool checkbox_checked)
|
||||
bool checkbox_checked,
|
||||
const gfx::ImageSkia& icon)
|
||||
: cancel_id_(cancel_id),
|
||||
checkbox_checked_(false),
|
||||
parent_(static_cast<NativeWindow*>(parent_window)) {
|
||||
@@ -56,6 +57,21 @@ class GtkMessageBox : public NativeWindowObserver {
|
||||
if (!title.empty())
|
||||
gtk_window_set_title(GTK_WINDOW(dialog_), title.c_str());
|
||||
|
||||
if (!icon.isNull()) {
|
||||
// No easy way to obtain this programmatically, but GTK+'s docs
|
||||
// define GTK_ICON_SIZE_DIALOG to be 48 pixels
|
||||
static constexpr int pixel_width = 48;
|
||||
static constexpr int pixel_height = 48;
|
||||
GdkPixbuf* pixbuf = libgtkui::GdkPixbufFromSkBitmap(*icon.bitmap());
|
||||
GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
|
||||
pixbuf, pixel_width, pixel_height, GDK_INTERP_BILINEAR);
|
||||
GtkWidget* w = gtk_image_new_from_pixbuf(scaled_pixbuf);
|
||||
gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), w);
|
||||
gtk_widget_show(w);
|
||||
g_clear_pointer(&scaled_pixbuf, gdk_pixbuf_unref);
|
||||
g_clear_pointer(&pixbuf, gdk_pixbuf_unref);
|
||||
}
|
||||
|
||||
if (!checkbox_label.empty()) {
|
||||
GtkWidget* message_area =
|
||||
gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_));
|
||||
@@ -66,15 +82,15 @@ class GtkMessageBox : public NativeWindowObserver {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
|
||||
checkbox_checked);
|
||||
gtk_container_add(GTK_CONTAINER(message_area), check_button);
|
||||
gtk_widget_show(check_button);
|
||||
}
|
||||
|
||||
// Add buttons.
|
||||
GtkDialog* dialog = GTK_DIALOG(dialog_);
|
||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||
GtkWidget* button = gtk_dialog_add_button(
|
||||
GTK_DIALOG(dialog_), TranslateToStock(i, buttons[i]), i);
|
||||
if (static_cast<int>(i) == default_id)
|
||||
gtk_widget_grab_focus(button);
|
||||
gtk_dialog_add_button(dialog, TranslateToStock(i, buttons[i]), i);
|
||||
}
|
||||
gtk_dialog_set_default_response(dialog, default_id);
|
||||
|
||||
// Parent window.
|
||||
if (parent_) {
|
||||
@@ -122,7 +138,7 @@ class GtkMessageBox : public NativeWindowObserver {
|
||||
}
|
||||
|
||||
void Show() {
|
||||
gtk_widget_show_all(dialog_);
|
||||
gtk_widget_show(dialog_);
|
||||
// We need to call gtk_window_present after making the widgets visible to
|
||||
// make sure window gets correctly raised and gets focus.
|
||||
int time = ui::X11EventSource::GetInstance()->GetTimestamp();
|
||||
@@ -195,9 +211,9 @@ int ShowMessageBox(NativeWindow* parent,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const gfx::ImageSkia& /*icon*/) {
|
||||
const gfx::ImageSkia& icon) {
|
||||
return GtkMessageBox(parent, type, buttons, default_id, cancel_id, title,
|
||||
message, detail, "", false)
|
||||
message, detail, "", false, icon)
|
||||
.RunSynchronous();
|
||||
}
|
||||
|
||||
@@ -212,10 +228,10 @@ void ShowMessageBox(NativeWindow* parent,
|
||||
const std::string& detail,
|
||||
const std::string& checkbox_label,
|
||||
bool checkbox_checked,
|
||||
const gfx::ImageSkia& /*icon*/,
|
||||
const gfx::ImageSkia& icon,
|
||||
const MessageBoxCallback& callback) {
|
||||
(new GtkMessageBox(parent, type, buttons, default_id, cancel_id, title,
|
||||
message, detail, checkbox_label, checkbox_checked))
|
||||
message, detail, checkbox_label, checkbox_checked, icon))
|
||||
->RunAsynchronous(callback);
|
||||
}
|
||||
|
||||
@@ -223,7 +239,8 @@ void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||
if (Browser::Get()->is_ready()) {
|
||||
GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, {"OK"}, -1, 0, "Error",
|
||||
base::UTF16ToUTF8(title).c_str(),
|
||||
base::UTF16ToUTF8(content).c_str(), "", false)
|
||||
base::UTF16ToUTF8(content).c_str(), "", false,
|
||||
gfx::ImageSkia())
|
||||
.RunSynchronous();
|
||||
} else {
|
||||
fprintf(stderr, ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY ANSI_FOREGROUND_RED
|
||||
|
||||
@@ -96,11 +96,6 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
|
||||
NSArray* ns_buttons = [alert buttons];
|
||||
int button_count = static_cast<int>([ns_buttons count]);
|
||||
|
||||
// Bind cancel id button to escape key if there is more than one button
|
||||
if (button_count > 1 && cancel_id >= 0 && cancel_id < button_count) {
|
||||
[[ns_buttons objectAtIndex:cancel_id] setKeyEquivalent:@"\e"];
|
||||
}
|
||||
|
||||
if (default_id >= 0 && default_id < button_count) {
|
||||
// Focus the button at default_id if the user opted to do so.
|
||||
// The first button added gets set as the default selected.
|
||||
@@ -109,6 +104,11 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
|
||||
[[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"];
|
||||
}
|
||||
|
||||
// Bind cancel id button to escape key if there is more than one button
|
||||
if (button_count > 1 && cancel_id >= 0 && cancel_id < button_count) {
|
||||
[[ns_buttons objectAtIndex:cancel_id] setKeyEquivalent:@"\e"];
|
||||
}
|
||||
|
||||
if (!checkbox_label.empty()) {
|
||||
alert.showsSuppressionButton = YES;
|
||||
alert.suppressionButton.title = base::SysUTF8ToNSString(checkbox_label);
|
||||
|
||||
@@ -40,9 +40,16 @@ const CGFloat kVerticalTitleMargin = 2;
|
||||
|
||||
@implementation StatusItemView
|
||||
|
||||
- (void)dealloc {
|
||||
trayIcon_ = nil;
|
||||
menuController_ = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithImage:(NSImage*)image icon:(atom::TrayIconCocoa*)icon {
|
||||
image_.reset([image copy]);
|
||||
trayIcon_ = icon;
|
||||
menuController_ = nil;
|
||||
highlight_mode_ = atom::TrayIcon::HighlightMode::SELECTION;
|
||||
forceHighlight_ = NO;
|
||||
inMouseEventSequence_ = NO;
|
||||
@@ -85,6 +92,7 @@ const CGFloat kVerticalTitleMargin = 2;
|
||||
trackingArea_.reset();
|
||||
}
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:statusItem_];
|
||||
[statusItem_ setView:nil];
|
||||
statusItem_.reset();
|
||||
}
|
||||
|
||||
@@ -454,11 +462,18 @@ void TrayIconCocoa::SetContextMenu(AtomMenuModel* menu_model) {
|
||||
// Substribe to MenuClosed event.
|
||||
if (menu_model_)
|
||||
menu_model_->RemoveObserver(this);
|
||||
menu_model->AddObserver(this);
|
||||
|
||||
// Create native menu.
|
||||
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model
|
||||
useDefaultAccelerator:NO]);
|
||||
menu_model_ = menu_model;
|
||||
|
||||
if (menu_model) {
|
||||
menu_model->AddObserver(this);
|
||||
// Create native menu.
|
||||
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model
|
||||
useDefaultAccelerator:NO]);
|
||||
} else {
|
||||
menu_.reset();
|
||||
}
|
||||
|
||||
[status_item_view_ setMenuController:menu_.get()];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
|
||||
#include "atom/browser/ui/views/menu_bar.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/ui/views/menu_delegate.h"
|
||||
#include "atom/browser/ui/views/submenu_button.h"
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
|
||||
#if defined(USE_X11)
|
||||
#include "chrome/browser/ui/libgtkui/gtk_util.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "ui/gfx/color_utils.h"
|
||||
#endif
|
||||
@@ -130,13 +137,17 @@ void MenuBar::RefreshColorCache(const ui::NativeTheme* theme) {
|
||||
if (!theme)
|
||||
theme = ui::NativeTheme::GetInstanceForNativeUi();
|
||||
if (theme) {
|
||||
background_color_ =
|
||||
theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBackgroundColor);
|
||||
#if defined(USE_X11)
|
||||
const std::string menubar_selector = "GtkMenuBar#menubar";
|
||||
background_color_ = libgtkui::GetBgColor(menubar_selector);
|
||||
|
||||
enabled_color_ = theme->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor);
|
||||
disabled_color_ = theme->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor);
|
||||
#else
|
||||
background_color_ =
|
||||
theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBackgroundColor);
|
||||
#endif
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
|
||||
@@ -201,8 +201,13 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
|
||||
|
||||
// --background-color.
|
||||
std::string color;
|
||||
if (web_preferences.GetString(options::kBackgroundColor, &color))
|
||||
if (web_preferences.GetString(options::kBackgroundColor, &color)) {
|
||||
command_line->AppendSwitchASCII(switches::kBackgroundColor, color);
|
||||
} else if (!(web_preferences.GetBoolean("offscreen", &b) && b)) {
|
||||
// For non-OSR WebContents, we expect to have white background, see
|
||||
// https://github.com/electron/electron/issues/13764 for more.
|
||||
command_line->AppendSwitchASCII(switches::kBackgroundColor, "#fff");
|
||||
}
|
||||
|
||||
// --guest-instance-id, which is used to identify guest WebContents.
|
||||
int guest_instance_id = 0;
|
||||
@@ -331,6 +336,11 @@ void WebContentsPreferences::OverrideWebkitPrefs(
|
||||
std::string encoding;
|
||||
if (self->web_preferences_.GetString("defaultEncoding", &encoding))
|
||||
prefs->default_encoding = encoding;
|
||||
|
||||
bool node_integration = false;
|
||||
self->web_preferences_.GetBoolean(options::kNodeIntegration,
|
||||
&node_integration);
|
||||
prefs->node_integration = node_integration;
|
||||
}
|
||||
|
||||
bool WebContentsPreferences::GetInteger(const std::string& attributeName,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#define ATOM_MAJOR_VERSION 2
|
||||
#define ATOM_MINOR_VERSION 0
|
||||
#define ATOM_PATCH_VERSION 6
|
||||
#define ATOM_PATCH_VERSION 13
|
||||
// #define ATOM_PRE_RELEASE_VERSION
|
||||
|
||||
#ifndef ATOM_STRINGIFY
|
||||
|
||||
@@ -168,6 +168,7 @@ void NodeBindings::Initialize() {
|
||||
|
||||
// Init node.
|
||||
// (we assume node::Init would not modify the parameters under embedded mode).
|
||||
// NOTE: If you change this line, please ping @codebytere or @MarshallOfSound
|
||||
node::Init(nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "atom/renderer/atom_render_frame_observer.h"
|
||||
#include "atom/renderer/web_worker_observer.h"
|
||||
#include "base/command_line.h"
|
||||
#include "content/public/common/web_preferences.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "third_party/WebKit/public/web/WebDocument.h"
|
||||
@@ -89,6 +90,15 @@ void AtomRendererClient::DidCreateScriptContext(
|
||||
if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame))
|
||||
return;
|
||||
|
||||
// Don't allow node integration if this is a child window and it does not have
|
||||
// node integration enabled. Otherwise we would have memory leak in the child
|
||||
// window since we don't clean up node environments.
|
||||
//
|
||||
// TODO(zcbenz): We shouldn't allow node integration even for the top frame.
|
||||
if (!render_frame->GetWebkitPreferences().node_integration &&
|
||||
render_frame->GetWebFrame()->Opener())
|
||||
return;
|
||||
|
||||
// Prepare the node bindings.
|
||||
if (!node_integration_initialized_) {
|
||||
node_integration_initialized_ = true;
|
||||
|
||||
@@ -266,16 +266,6 @@ void BrowserMainParts::PreMainMessageLoopStart() {
|
||||
}
|
||||
|
||||
void BrowserMainParts::PreMainMessageLoopRun() {
|
||||
// We already initialized feature list in PreEarlyInitialization(), but
|
||||
// the user JS script would not have had a chance to alter the command-line
|
||||
// switches at that point. Lets reinitialize it here to pick up the
|
||||
// command-line changes. Note that some Chromium code (e.g.
|
||||
// gpu_process_host.cc) queries the feature list between
|
||||
// PreEarlyInitialization() and here so the user script may not have
|
||||
// control over all features. Better than nothing though!
|
||||
base::FeatureList::ClearInstanceForTesting();
|
||||
InitializeFeatureList();
|
||||
|
||||
content::WebUIControllerFactory::RegisterFactory(
|
||||
WebUIControllerFactory::GetInstance());
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ class BrowserMainParts : public content::BrowserMainParts {
|
||||
int PreCreateThreads() override;
|
||||
void PostDestroyThreads() override;
|
||||
|
||||
void InitializeFeatureList();
|
||||
|
||||
private:
|
||||
#if defined(OS_MACOSX)
|
||||
void InitializeMainNib();
|
||||
void OverrideAppLogsPath();
|
||||
#endif
|
||||
|
||||
void InitializeFeatureList();
|
||||
|
||||
std::unique_ptr<IOThread> io_thread_;
|
||||
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
|
||||
@@ -698,6 +698,8 @@ void InspectableWebContentsImpl::WebContentsDestroyed() {
|
||||
for (const auto& pair : pending_requests_)
|
||||
delete pair.first;
|
||||
|
||||
pending_requests_.clear();
|
||||
|
||||
if (view_ && view_->GetDelegate())
|
||||
view_->GetDelegate()->DevToolsClosed();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> Define keyboard shortcuts.
|
||||
|
||||
Accelerators are Strings that can contain multiple modifiers and key codes,
|
||||
Accelerators are Strings that can contain multiple modifiers and a single key code,
|
||||
combined by the `+` character, and are used to define keyboard shortcuts
|
||||
throughout your application.
|
||||
|
||||
|
||||
@@ -365,6 +365,22 @@ assistive technologies, such as screen readers, are enabled or disabled.
|
||||
See https://www.chromium.org/developers/design-documents/accessibility for more
|
||||
details.
|
||||
|
||||
### Event: 'session-created'
|
||||
|
||||
Returns:
|
||||
|
||||
* `session` [Session](session.md)
|
||||
|
||||
Emitted when Electron has created a new `session`.
|
||||
|
||||
```javascript
|
||||
const {app} = require('electron')
|
||||
|
||||
app.on('session-created', (event, session) => {
|
||||
console.log(session)
|
||||
})
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
The `app` object has the following methods:
|
||||
|
||||
@@ -202,7 +202,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
than screen. Default is `false`.
|
||||
* `backgroundColor` String (optional) - Window's background color as a hexadecimal value,
|
||||
like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported). Default is
|
||||
`#FFF` (white).
|
||||
`#FFF` (white). If `transparent` is set to `true`, only values with transparent (`#00-------`) or opaque (`#FF-----`) alpha values are respected.
|
||||
* `hasShadow` Boolean (optional) - Whether window should have a shadow. This is only
|
||||
implemented on macOS. Default is `true`.
|
||||
* `opacity` Number (optional) - Set the initial opacity of the window, between 0.0 (fully
|
||||
@@ -361,7 +361,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
script. You can use the `will-attach-webview` event on [webContents](web-contents.md)
|
||||
to strip away the `preload` script and to validate or alter the
|
||||
`<webview>`'s initial settings.
|
||||
* `additionArguments` String[] (optional) - A list of strings that will be appended
|
||||
* `additionalArguments` String[] (optional) - A list of strings that will be appended
|
||||
to `process.argv` in the renderer process of this app. Useful for passing small
|
||||
bits of data down to renderer process preload scripts.
|
||||
|
||||
@@ -870,7 +870,9 @@ Disable or enable the window.
|
||||
* `height` Integer
|
||||
* `animate` Boolean (optional) _macOS_
|
||||
|
||||
Resizes the window to `width` and `height`.
|
||||
Resizes the window to `width` and `height`. If `width` or `height`
|
||||
are below any set minimum size constraints the window will snap to
|
||||
its minimum size.
|
||||
|
||||
#### `win.getSize()`
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ When specifying a `role` on macOS, `label` and `accelerator` are the only
|
||||
options that will affect the menu item. All other options will be ignored.
|
||||
Lowercase `role`, e.g. `toggledevtools`, is still supported.
|
||||
|
||||
**Nota Bene:** The `enabled` and `visibility` properties are not available for top-level menu items in the tray on MacOS.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
The following properties are available on instances of `MenuItem`:
|
||||
|
||||
@@ -67,6 +67,8 @@ that contains the user information dictionary sent along with the notification.
|
||||
* `callback` Function
|
||||
* `event` String
|
||||
* `userInfo` Object
|
||||
|
||||
Returns `Number` - The ID of this subscription
|
||||
|
||||
Subscribes to native notifications of macOS, `callback` will be called with
|
||||
`callback(event, userInfo)` when the corresponding `event` happens. The
|
||||
@@ -96,6 +98,8 @@ Removes the subscriber with `id`.
|
||||
* `callback` Function
|
||||
* `event` String
|
||||
* `userInfo` Object
|
||||
|
||||
Returns `Number` - The ID of this subscription
|
||||
|
||||
Same as `subscribeNotification`, but uses `NSNotificationCenter` for local defaults.
|
||||
This is necessary for events such as `NSUserDefaultsDidChangeNotification`.
|
||||
|
||||
@@ -262,7 +262,7 @@ The `position` is only available on Windows, and it is (0, 0) by default.
|
||||
|
||||
#### `tray.setContextMenu(menu)`
|
||||
|
||||
* `menu` Menu
|
||||
* `menu` Menu | null
|
||||
|
||||
Sets the context menu for this icon.
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ Set the security origin of the isolated world.
|
||||
Returns `Object`:
|
||||
|
||||
* `images` [MemoryUsageDetails](structures/memory-usage-details.md)
|
||||
* `scripts` [MemoryUsageDetails](structures/memory-usage-details.md)
|
||||
* `cssStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md)
|
||||
* `xslStyleSheets` [MemoryUsageDetails](structures/memory-usage-details.md)
|
||||
* `fonts` [MemoryUsageDetails](structures/memory-usage-details.md)
|
||||
|
||||
@@ -24,7 +24,12 @@ Returns [`BrowserWindowProxy`](browser-window-proxy.md) - Creates a new window
|
||||
and returns an instance of `BrowserWindowProxy` class.
|
||||
|
||||
The `features` string follows the format of standard browser, but each feature
|
||||
has to be a field of `BrowserWindow`'s options.
|
||||
has to be a field of `BrowserWindow`'s options. These are the features you can set via `features` string: `zoomFactor`, `nodeIntegration`, `preload`, `javascript`, `contextIsolation`, `webviewTag`.
|
||||
|
||||
For example:
|
||||
```js
|
||||
window.open('https://github.com', '_blank', 'nodeIntegration=no')
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
'product_name%': 'Electron',
|
||||
'company_name%': 'GitHub, Inc',
|
||||
'company_abbr%': 'github',
|
||||
'version%': '2.0.6',
|
||||
'version%': '2.0.13',
|
||||
'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c',
|
||||
},
|
||||
'includes': [
|
||||
|
||||
@@ -49,6 +49,21 @@ BrowserWindow.prototype._init = function () {
|
||||
return
|
||||
}
|
||||
|
||||
if (webContents.getLastWebPreferences().nodeIntegration === true) {
|
||||
const message =
|
||||
'Enabling Node.js integration in child windows opened with the ' +
|
||||
'"nativeWindowOpen" option will cause memory leaks, please turn off ' +
|
||||
'the "nodeIntegration" option.\\n' +
|
||||
'See https://github.com/electron/electron/pull/15076 for more.'
|
||||
// console is only available after DOM is created.
|
||||
const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
|
||||
if (this.webContents.isDomReady()) {
|
||||
printWarning()
|
||||
} else {
|
||||
this.webContents.once('dom-ready', printWarning)
|
||||
}
|
||||
}
|
||||
|
||||
let {url, frameName} = urlFrameName
|
||||
v8Util.deleteHiddenValue(webContents, 'url-framename')
|
||||
const options = {
|
||||
|
||||
@@ -27,11 +27,11 @@ const mergeOptions = function (child, parent, visited) {
|
||||
for (const key in parent) {
|
||||
if (key === 'isBrowserView') continue
|
||||
if (!hasProp.call(parent, key)) continue
|
||||
if (key in child) continue
|
||||
if (key in child && key !== 'webPreferences') continue
|
||||
|
||||
const value = parent[key]
|
||||
if (typeof value === 'object') {
|
||||
child[key] = mergeOptions({}, value, visited)
|
||||
child[key] = mergeOptions(child[key] || {}, value, visited)
|
||||
} else {
|
||||
child[key] = value
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ class ObjectsRegistry {
|
||||
|
||||
// Private: Dereference the object from store.
|
||||
dereference (id) {
|
||||
if (process.env.ELECTRON_DISABLE_REMOTE_DEREFERENCING) return
|
||||
|
||||
let pointer = this.storage[id]
|
||||
if (pointer == null) {
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const {Buffer} = require('buffer')
|
||||
const childProcess = require('child_process')
|
||||
const path = require('path')
|
||||
const util = require('util')
|
||||
|
||||
const hasProp = {}.hasOwnProperty
|
||||
|
||||
@@ -66,47 +67,40 @@
|
||||
let nextInode = 0
|
||||
|
||||
const uid = process.getuid != null ? process.getuid() : 0
|
||||
|
||||
const gid = process.getgid != null ? process.getgid() : 0
|
||||
|
||||
const fakeTime = new Date()
|
||||
const msec = (date) => (date || fakeTime).getTime()
|
||||
|
||||
const asarStatsToFsStats = function (stats) {
|
||||
return {
|
||||
dev: 1,
|
||||
ino: ++nextInode,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
rdev: 0,
|
||||
atime: stats.atime || fakeTime,
|
||||
birthtime: stats.birthtime || fakeTime,
|
||||
mtime: stats.mtime || fakeTime,
|
||||
ctime: stats.ctime || fakeTime,
|
||||
size: stats.size,
|
||||
isFile: function () {
|
||||
return stats.isFile
|
||||
},
|
||||
isDirectory: function () {
|
||||
return stats.isDirectory
|
||||
},
|
||||
isSymbolicLink: function () {
|
||||
return stats.isLink
|
||||
},
|
||||
isBlockDevice: function () {
|
||||
return false
|
||||
},
|
||||
isCharacterDevice: function () {
|
||||
return false
|
||||
},
|
||||
isFIFO: function () {
|
||||
return false
|
||||
},
|
||||
isSocket: function () {
|
||||
return false
|
||||
}
|
||||
const {Stats, constants} = require('fs')
|
||||
|
||||
let mode = constants.S_IROTH ^ constants.S_IRGRP ^ constants.S_IRUSR ^ constants.S_IWUSR
|
||||
|
||||
if (stats.isFile) {
|
||||
mode ^= constants.S_IFREG
|
||||
} else if (stats.isDirectory) {
|
||||
mode ^= constants.S_IFDIR
|
||||
} else if (stats.isLink) {
|
||||
mode ^= constants.S_IFLNK
|
||||
}
|
||||
|
||||
return new Stats(
|
||||
1, // dev
|
||||
mode, // mode
|
||||
1, // nlink
|
||||
uid,
|
||||
gid,
|
||||
0, // rdev
|
||||
undefined, // blksize
|
||||
++nextInode, // ino
|
||||
stats.size,
|
||||
undefined, // blocks,
|
||||
msec(stats.atime), // atim_msec
|
||||
msec(stats.mtime), // mtim_msec
|
||||
msec(stats.ctime), // ctim_msec
|
||||
msec(stats.birthtime) // birthtim_msec
|
||||
)
|
||||
}
|
||||
|
||||
// Create a ENOENT error.
|
||||
@@ -217,6 +211,28 @@
|
||||
arguments[arg] = newPath
|
||||
return old.apply(this, arguments)
|
||||
}
|
||||
if (old[util.promisify.custom]) {
|
||||
module[name][util.promisify.custom] = function () {
|
||||
const p = arguments[arg]
|
||||
const [isAsar, asarPath, filePath] = splitPath(p)
|
||||
if (!isAsar) {
|
||||
return old[util.promisify.custom].apply(this, arguments)
|
||||
}
|
||||
|
||||
const archive = getOrCreateArchive(asarPath)
|
||||
if (!archive) {
|
||||
return new Promise(() => invalidArchiveError(asarPath))
|
||||
}
|
||||
|
||||
const newPath = archive.copyFileOut(filePath)
|
||||
if (!newPath) {
|
||||
return new Promise(() => notFoundError(asarPath, filePath))
|
||||
}
|
||||
|
||||
arguments[arg] = newPath
|
||||
return old[util.promisify.custom].apply(this, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override fs APIs.
|
||||
@@ -373,6 +389,18 @@
|
||||
})
|
||||
}
|
||||
|
||||
fs.exists[util.promisify.custom] = function (p) {
|
||||
const [isAsar, asarPath, filePath] = splitPath(p)
|
||||
if (!isAsar) {
|
||||
return exists[util.promisify.custom](p)
|
||||
}
|
||||
const archive = getOrCreateArchive(asarPath)
|
||||
if (!archive) {
|
||||
return new Promise(() => invalidArchiveError(asarPath))
|
||||
}
|
||||
return Promise.resolve(archive.stat(filePath) !== false)
|
||||
}
|
||||
|
||||
const {existsSync} = fs
|
||||
fs.existsSync = function (p) {
|
||||
const [isAsar, asarPath, filePath] = splitPath(p)
|
||||
@@ -680,18 +708,28 @@
|
||||
// called by `childProcess.{exec,execSync}`, causing
|
||||
// Electron to consider the full command as a single path
|
||||
// to an archive.
|
||||
['exec', 'execSync'].forEach(function (functionName) {
|
||||
const old = childProcess[functionName]
|
||||
childProcess[functionName] = function () {
|
||||
const {exec, execSync} = childProcess
|
||||
childProcess.exec = invokeWithNoAsar(exec)
|
||||
childProcess.exec[util.promisify.custom] = invokeWithNoAsar(exec[util.promisify.custom])
|
||||
childProcess.execSync = invokeWithNoAsar(execSync)
|
||||
|
||||
function invokeWithNoAsar (func) {
|
||||
return function () {
|
||||
const processNoAsarOriginalValue = process.noAsar
|
||||
process.noAsar = true
|
||||
try {
|
||||
return old.apply(this, arguments)
|
||||
return func.apply(this, arguments)
|
||||
} finally {
|
||||
process.noAsar = processNoAsarOriginalValue
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Strictly implementing the flags of fs.copyFile is hard, just do a simple
|
||||
// implementation for now. Doing 2 copies won't spend much time more as OS
|
||||
// has filesystem caching.
|
||||
overrideAPI(fs, 'copyFile')
|
||||
overrideAPISync(fs, 'copyFileSync')
|
||||
|
||||
overrideAPI(fs, 'open')
|
||||
overrideAPI(childProcess, 'execFile')
|
||||
|
||||
@@ -3,8 +3,7 @@ window.onload = function () {
|
||||
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
|
||||
|
||||
// Use dialog API to override file chooser dialog.
|
||||
// Note: It will be moved to UI after Chrome 57.
|
||||
window.Bindings.createFileSelectorElement = createFileSelectorElement
|
||||
window.UI.createFileSelectorElement = createFileSelectorElement
|
||||
}
|
||||
|
||||
window.confirm = function (message, title) {
|
||||
|
||||
@@ -145,9 +145,7 @@ class WebViewImpl {
|
||||
|
||||
onElementResize (newSize) {
|
||||
// Dispatch the 'resize' event.
|
||||
const resizeEvent = new Event('resize', {
|
||||
bubbles: true
|
||||
})
|
||||
const resizeEvent = new Event('resize')
|
||||
|
||||
// Using client size values, because when a webview is transformed `newSize`
|
||||
// is incorrect
|
||||
|
||||
11
npm/cli.js
11
npm/cli.js
@@ -8,3 +8,14 @@ var child = proc.spawn(electron, process.argv.slice(2), {stdio: 'inherit'})
|
||||
child.on('close', function (code) {
|
||||
process.exit(code)
|
||||
})
|
||||
|
||||
const handleTerminationSignal = function (signal) {
|
||||
process.on(signal, function signalHandler () {
|
||||
if (!child.killed) {
|
||||
child.kill(signal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleTerminationSignal('SIGINT')
|
||||
handleTerminationSignal('SIGTERM')
|
||||
|
||||
@@ -21,6 +21,12 @@ if (installedVersion === version && fs.existsSync(path.join(__dirname, platformP
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
var mirror
|
||||
|
||||
if (version.indexOf('nightly') !== -1) {
|
||||
mirror = 'https://github.com/electron/nightlies/releases/download/v'
|
||||
}
|
||||
|
||||
// downloads if not cached
|
||||
download({
|
||||
cache: process.env.electron_config_cache,
|
||||
@@ -29,7 +35,8 @@ download({
|
||||
arch: process.env.npm_config_arch,
|
||||
strictSSL: process.env.npm_config_strict_ssl === 'true',
|
||||
force: process.env.force_no_cache === 'true',
|
||||
quiet: process.env.npm_config_loglevel === 'silent' || process.env.CI
|
||||
quiet: process.env.npm_config_loglevel === 'silent' || process.env.CI,
|
||||
mirror
|
||||
}, extractFile)
|
||||
|
||||
// unzips and makes path.txt point at the correct executable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.13",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,12 +5,12 @@ import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from lib.util import execute, get_electron_version, parse_version, scoped_cwd
|
||||
|
||||
from lib.util import execute, get_electron_version, parse_version, scoped_cwd, \
|
||||
is_nightly, is_beta, is_stable, get_next_nightly, get_next_beta, \
|
||||
get_next_stable_from_pre, get_next_stable_from_stable, clean_parse_version
|
||||
|
||||
SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -34,14 +34,7 @@ def main():
|
||||
action='store',
|
||||
default=None,
|
||||
dest='bump',
|
||||
help='increment [major | minor | patch | beta]'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--stable',
|
||||
action='store_true',
|
||||
default= False,
|
||||
dest='stable',
|
||||
help='promote to stable (i.e. remove `-beta.x` suffix)'
|
||||
help='increment [stable | beta | nightly]'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
@@ -52,36 +45,56 @@ def main():
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
curr_version = get_electron_version()
|
||||
|
||||
if args.bump not in ['stable', 'beta', 'nightly']:
|
||||
raise Exception('bump must be set to either stable, beta or nightly')
|
||||
|
||||
if is_nightly(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
version = get_next_beta(curr_version)
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_pre(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
elif is_beta(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
version = get_next_beta(curr_version)
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_pre(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
elif is_stable(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
raise Exception("You can\'t bump to a beta from stable")
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_stable(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
else:
|
||||
raise Exception("Invalid current version: " + curr_version)
|
||||
|
||||
if args.new_version == None and args.bump == None and args.stable == False:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
increments = ['major', 'minor', 'patch', 'beta']
|
||||
|
||||
curr_version = get_electron_version()
|
||||
versions = parse_version(re.sub('-beta', '', curr_version))
|
||||
|
||||
if args.bump in increments:
|
||||
versions = increase_version(versions, increments.index(args.bump))
|
||||
if versions[3] == '0':
|
||||
# beta starts at 1
|
||||
versions = increase_version(versions, increments.index('beta'))
|
||||
|
||||
if args.stable == True:
|
||||
versions[3] = '0'
|
||||
|
||||
if args.new_version != None:
|
||||
versions = parse_version(re.sub('-beta', '', args.new_version))
|
||||
|
||||
version = '.'.join(versions[:3])
|
||||
suffix = '' if versions[3] == '0' else '-beta.' + versions[3]
|
||||
versions = clean_parse_version(version)
|
||||
suffix = ''
|
||||
if '-' in version:
|
||||
suffix = '-' + version.split('-')[1]
|
||||
versions[3] = parse_version(version)[3]
|
||||
version = version.split('-')[0]
|
||||
|
||||
if args.dry_run:
|
||||
print 'new version number would be: {0}\n'.format(version + suffix)
|
||||
return 0
|
||||
|
||||
|
||||
with scoped_cwd(SOURCE_ROOT):
|
||||
update_electron_gyp(version, suffix)
|
||||
update_win_rc(version, versions)
|
||||
@@ -92,6 +105,9 @@ def main():
|
||||
|
||||
print 'Bumped to version: {0}'.format(version + suffix)
|
||||
|
||||
def not_reached():
|
||||
raise Exception('Unreachable code was reached')
|
||||
|
||||
def increase_version(versions, index):
|
||||
for i in range(index + 1, 4):
|
||||
versions[i] = '0'
|
||||
@@ -100,7 +116,8 @@ def increase_version(versions, index):
|
||||
|
||||
|
||||
def update_electron_gyp(version, suffix):
|
||||
pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?'")
|
||||
pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?(-dev)?"
|
||||
+ "(-nightly[0-9.]*)?'")
|
||||
with open('electron.gyp', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
@@ -192,7 +209,14 @@ def update_package_json(version, suffix):
|
||||
|
||||
|
||||
def tag_version(version, suffix):
|
||||
execute(['git', 'commit', '-a', '-m', 'Bump v{0}'.format(version + suffix)])
|
||||
execute([
|
||||
'git',
|
||||
'commit',
|
||||
'-a',
|
||||
'-m',
|
||||
'Bump v{0}'.format(version + suffix),
|
||||
'-n'
|
||||
])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require('dotenv-safe').load()
|
||||
|
||||
const assert = require('assert')
|
||||
const request = require('request')
|
||||
const buildAppVeyorURL = 'https://windows-ci.electronjs.org/api/builds'
|
||||
@@ -32,8 +34,13 @@ async function makeRequest (requestOptions, parseResponse) {
|
||||
resolve(body)
|
||||
}
|
||||
} else {
|
||||
console.error('Error occurred while requesting:', requestOptions.url)
|
||||
if (parseResponse) {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
|
||||
try {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
|
||||
} catch (err) {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
|
||||
}
|
||||
} else {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
|
||||
}
|
||||
@@ -44,7 +51,6 @@ async function makeRequest (requestOptions, parseResponse) {
|
||||
}
|
||||
|
||||
async function circleCIcall (buildUrl, targetBranch, job, options) {
|
||||
assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment')
|
||||
console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`)
|
||||
let buildRequest = {
|
||||
'build_parameters': {
|
||||
@@ -77,7 +83,6 @@ async function circleCIcall (buildUrl, targetBranch, job, options) {
|
||||
}
|
||||
|
||||
function buildAppVeyor (targetBranch, options) {
|
||||
assert(process.env.APPVEYOR_TOKEN, 'APPVEYOR_TOKEN not found in environment')
|
||||
const validJobs = Object.keys(appVeyorJobs)
|
||||
if (options.job) {
|
||||
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`)
|
||||
@@ -139,7 +144,6 @@ async function buildVSTS (targetBranch, options) {
|
||||
assert(vstsJobs.includes(options.job), `Unknown VSTS CI job name: ${options.job}. Valid values are: ${vstsJobs}.`)
|
||||
}
|
||||
console.log(`Triggering VSTS to run build on branch: ${targetBranch} with release flag.`)
|
||||
assert(process.env.VSTS_TOKEN, 'VSTS_TOKEN not found in environment')
|
||||
let environmentVariables = {}
|
||||
|
||||
if (!options.ghRelease) {
|
||||
@@ -175,7 +179,8 @@ async function buildVSTS (targetBranch, options) {
|
||||
async function callVSTSBuild (build, targetBranch, environmentVariables) {
|
||||
let buildBody = {
|
||||
definition: build,
|
||||
sourceBranch: targetBranch
|
||||
sourceBranch: targetBranch,
|
||||
priority: 'high'
|
||||
}
|
||||
if (Object.keys(environmentVariables).length !== 0) {
|
||||
buildBody.parameters = JSON.stringify(environmentVariables)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
|
||||
const GitHub = require('github')
|
||||
const github = new GitHub()
|
||||
|
||||
@@ -12,7 +14,7 @@ async function findRelease () {
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
let releases = await github.repos.getReleases({
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
repo: version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
})
|
||||
let targetRelease = releases.data.find(release => {
|
||||
return release.tag_name === version
|
||||
|
||||
29
script/get-last-major-for-master.js
Normal file
29
script/get-last-major-for-master.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const { GitProcess } = require('dugite')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
|
||||
async function determineNextMajorForMaster () {
|
||||
let branchNames
|
||||
let result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]-[0-9]-x'], gitDir)
|
||||
if (result.exitCode === 0) {
|
||||
branchNames = result.stdout.trim().split('\n')
|
||||
const filtered = branchNames.map(b => b.replace('origin/', ''))
|
||||
return getNextReleaseBranch(filtered)
|
||||
} else {
|
||||
throw new Error('Release branches could not be fetched.')
|
||||
}
|
||||
}
|
||||
|
||||
function getNextReleaseBranch (branches) {
|
||||
const converted = branches.map(b => b.replace(/-/g, '.').replace('x', '0'))
|
||||
const next = converted.reduce((v1, v2) => {
|
||||
return semver.gt(v1, v2) ? v1 : v2
|
||||
})
|
||||
return parseInt(next.split('.')[0], 10)
|
||||
}
|
||||
|
||||
determineNextMajorForMaster().then(console.info).catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import atexit
|
||||
import contextlib
|
||||
import datetime
|
||||
import errno
|
||||
import platform
|
||||
import re
|
||||
@@ -87,7 +88,7 @@ def download(text, url, path):
|
||||
downloaded_size = 0
|
||||
block_size = 128
|
||||
|
||||
ci = os.environ.get('CI') == '1'
|
||||
ci = os.environ.get('CI') is not None
|
||||
|
||||
while True:
|
||||
buf = web_file.read(block_size)
|
||||
@@ -287,3 +288,67 @@ def update_node_modules(dirname, env=None):
|
||||
pass
|
||||
else:
|
||||
execute_stdout(args, env)
|
||||
|
||||
def clean_parse_version(v):
|
||||
return parse_version(v.split("-")[0])
|
||||
|
||||
def is_stable(v):
|
||||
return len(v.split(".")) == 3
|
||||
|
||||
def is_beta(v):
|
||||
return 'beta' in v
|
||||
|
||||
def is_nightly(v):
|
||||
return 'nightly' in v
|
||||
|
||||
def get_nightly_date():
|
||||
return datetime.datetime.today().strftime('%Y%m%d')
|
||||
|
||||
def get_last_major():
|
||||
return execute(['node', 'script/get-last-major-for-master.js'])
|
||||
|
||||
def get_next_nightly(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
|
||||
if (is_stable(v)):
|
||||
patch = str(int(pv[2]) + 1)
|
||||
|
||||
if execute(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) == "master":
|
||||
major = str(get_last_major() + 1)
|
||||
minor = '0'
|
||||
patch = '0'
|
||||
|
||||
pre = 'nightly.' + get_nightly_date()
|
||||
return make_version(major, minor, patch, pre)
|
||||
|
||||
def non_empty(thing):
|
||||
return thing.strip() != ''
|
||||
|
||||
def get_next_beta(v):
|
||||
pv = clean_parse_version(v)
|
||||
tag_pattern = 'v' + pv[0] + '.' + pv[1] + '.' + pv[2] + '-beta.*'
|
||||
tag_list = filter(
|
||||
non_empty,
|
||||
execute(['git', 'tag', '--list', '-l', tag_pattern]).strip().split('\n')
|
||||
)
|
||||
if len(tag_list) == 0:
|
||||
return make_version(pv[0] , pv[1], pv[2], 'beta.1')
|
||||
|
||||
lv = parse_version(tag_list[-1])
|
||||
return make_version(pv[0] , pv[1], pv[2], 'beta.' + str(int(lv[3]) + 1))
|
||||
|
||||
def get_next_stable_from_pre(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
return make_version(major, minor, patch)
|
||||
|
||||
def get_next_stable_from_stable(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
return make_version(major, minor, str(int(patch) + 1))
|
||||
|
||||
def make_version(major, minor, patch, pre = None):
|
||||
if pre is None:
|
||||
return major + '.' + minor + '.' + patch
|
||||
return major + "." + minor + "." + patch + '-' + pre
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
boolean: ['automaticRelease', 'notesOnly', 'stable']
|
||||
})
|
||||
const assert = require('assert')
|
||||
const ciReleaseBuild = require('./ci-release-build')
|
||||
const { execSync } = require('child_process')
|
||||
const fail = '\u2717'.red
|
||||
@@ -15,13 +15,13 @@ const path = require('path')
|
||||
const pkg = require('../package.json')
|
||||
const readline = require('readline')
|
||||
const versionType = args._[0]
|
||||
const targetRepo = versionType === 'nightly' ? 'nightlies' : 'electron'
|
||||
|
||||
// TODO (future) automatically determine version based on conventional commits
|
||||
// via conventional-recommended-bump
|
||||
|
||||
assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
|
||||
if (!versionType && !args.notesOnly) {
|
||||
console.log(`Usage: prepare-release versionType [major | minor | patch | beta]` +
|
||||
console.log(`Usage: prepare-release versionType [stable | beta | nightly]` +
|
||||
` (--stable) (--notesOnly) (--automaticRelease) (--branch)`)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -30,13 +30,12 @@ const github = new GitHub()
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
|
||||
function getNewVersion (dryRun) {
|
||||
console.log(`Bumping for new "${versionType}" version.`)
|
||||
let bumpScript = path.join(__dirname, 'bump-version.py')
|
||||
let scriptArgs = [bumpScript, `--bump ${versionType}`]
|
||||
if (args.stable) {
|
||||
scriptArgs.push('--stable')
|
||||
async function getNewVersion (dryRun) {
|
||||
if (!dryRun) {
|
||||
console.log(`Bumping for new "${versionType}" version.`)
|
||||
}
|
||||
let bumpScript = path.join(__dirname, 'bump-version.py')
|
||||
let scriptArgs = [bumpScript, '--bump', versionType]
|
||||
if (dryRun) {
|
||||
scriptArgs.push('--dry-run')
|
||||
}
|
||||
@@ -50,6 +49,7 @@ function getNewVersion (dryRun) {
|
||||
return newVersion
|
||||
} catch (err) {
|
||||
console.log(`${fail} Could not bump version, error was:`, err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,13 @@ async function getCurrentBranch (gitDir) {
|
||||
}
|
||||
|
||||
async function getReleaseNotes (currentBranch) {
|
||||
if (versionType === 'nightly') {
|
||||
return 'Nightlies do not get release notes, please compare tags for info'
|
||||
}
|
||||
console.log(`Generating release notes for ${currentBranch}.`)
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
base: `v${pkg.version}`,
|
||||
head: currentBranch
|
||||
}
|
||||
@@ -136,11 +139,11 @@ async function getReleaseNotes (currentBranch) {
|
||||
|
||||
async function createRelease (branchToTarget, isBeta) {
|
||||
let releaseNotes = await getReleaseNotes(branchToTarget)
|
||||
let newVersion = getNewVersion()
|
||||
let newVersion = await getNewVersion()
|
||||
await tagRelease(newVersion)
|
||||
const githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
repo: targetRepo
|
||||
}
|
||||
console.log(`Checking for existing draft release.`)
|
||||
let releases = await github.repos.getReleases(githubOpts)
|
||||
@@ -158,17 +161,24 @@ async function createRelease (branchToTarget, isBeta) {
|
||||
githubOpts.draft = true
|
||||
githubOpts.name = `electron ${newVersion}`
|
||||
if (isBeta) {
|
||||
githubOpts.body = `Note: This is a beta release. Please file new issues ` +
|
||||
`for any bugs you find in it.\n \n This release is published to npm ` +
|
||||
`under the beta tag and can be installed via npm install electron@beta, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
if (newVersion.indexOf('nightly') > 0) {
|
||||
githubOpts.body = `Note: This is a nightly release. Please file new issues ` +
|
||||
`for any bugs you find in it.\n \n This release is published to npm ` +
|
||||
`under the nightly tag and can be installed via npm install electron@nightly, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
} else {
|
||||
githubOpts.body = `Note: This is a beta release. Please file new issues ` +
|
||||
`for any bugs you find in it.\n \n This release is published to npm ` +
|
||||
`under the beta tag and can be installed via npm install electron@beta, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
}
|
||||
githubOpts.name = `${githubOpts.name}`
|
||||
githubOpts.prerelease = true
|
||||
} else {
|
||||
githubOpts.body = releaseNotes
|
||||
}
|
||||
githubOpts.tag_name = newVersion
|
||||
githubOpts.target_commitish = branchToTarget
|
||||
githubOpts.target_commitish = newVersion.indexOf('nightly') !== -1 ? 'master' : branchToTarget
|
||||
await github.repos.createRelease(githubOpts)
|
||||
.catch(err => {
|
||||
console.log(`${fail} Error creating new release: `, err)
|
||||
@@ -209,7 +219,7 @@ async function tagRelease (version) {
|
||||
}
|
||||
|
||||
async function verifyNewVersion () {
|
||||
let newVersion = getNewVersion(true)
|
||||
let newVersion = await getNewVersion(true)
|
||||
let response
|
||||
if (args.automaticRelease) {
|
||||
response = 'y'
|
||||
@@ -237,26 +247,34 @@ async function promptForVersion (version) {
|
||||
})
|
||||
}
|
||||
|
||||
// function to determine if there have been commits to master since the last release
|
||||
async function changesToRelease () {
|
||||
let lastCommitWasRelease = new RegExp(`^Bump v[0-9.]*(-beta[0-9.]*)?(-nightly[0-9.]*)?$`, 'g')
|
||||
let lastCommit = await GitProcess.exec(['log', '-n', '1', `--pretty=format:'%s'`], gitDir)
|
||||
return !lastCommitWasRelease.test(lastCommit.stdout)
|
||||
}
|
||||
|
||||
async function prepareRelease (isBeta, notesOnly) {
|
||||
if (args.automaticRelease && (pkg.version.indexOf('beta') === -1 ||
|
||||
versionType !== 'beta')) {
|
||||
console.log(`${fail} Automatic release is only supported for beta releases`)
|
||||
process.exit(1)
|
||||
}
|
||||
let currentBranch
|
||||
if (args.branch) {
|
||||
currentBranch = args.branch
|
||||
if (args.dryRun) {
|
||||
let newVersion = await getNewVersion(true)
|
||||
console.log(newVersion)
|
||||
} else {
|
||||
currentBranch = await getCurrentBranch(gitDir)
|
||||
}
|
||||
if (notesOnly) {
|
||||
let releaseNotes = await getReleaseNotes(currentBranch)
|
||||
console.log(`Draft release notes are: \n${releaseNotes}`)
|
||||
} else {
|
||||
await verifyNewVersion()
|
||||
await createRelease(currentBranch, isBeta)
|
||||
await pushRelease(currentBranch)
|
||||
await runReleaseBuilds(currentBranch)
|
||||
const currentBranch = (args.branch) ? args.branch : await getCurrentBranch(gitDir)
|
||||
if (notesOnly) {
|
||||
let releaseNotes = await getReleaseNotes(currentBranch)
|
||||
console.log(`Draft release notes are: \n${releaseNotes}`)
|
||||
} else {
|
||||
const changes = await changesToRelease(currentBranch)
|
||||
if (changes) {
|
||||
await verifyNewVersion()
|
||||
await createRelease(currentBranch, isBeta)
|
||||
await pushRelease(currentBranch)
|
||||
await runReleaseBuilds(currentBranch)
|
||||
} else {
|
||||
console.log(`There are no new changes to this branch since the last release, aborting release.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const childProcess = require('child_process')
|
||||
const GitHubApi = require('github')
|
||||
const {GitProcess} = require('dugite')
|
||||
const request = require('request')
|
||||
const assert = require('assert')
|
||||
const rootPackageJson = require('../package.json')
|
||||
|
||||
if (!process.env.ELECTRON_NPM_OTP) {
|
||||
console.error('Please set ELECTRON_NPM_OTP')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const github = new GitHubApi({
|
||||
// debug: true,
|
||||
headers: { 'User-Agent': 'electron-npm-publisher' },
|
||||
@@ -68,7 +73,7 @@ new Promise((resolve, reject) => {
|
||||
|
||||
return github.repos.getReleases({
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
repo: rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
})
|
||||
})
|
||||
.then((releases) => {
|
||||
@@ -103,8 +108,17 @@ new Promise((resolve, reject) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
.then((release) => {
|
||||
npmTag = release.prerelease ? 'beta' : 'latest'
|
||||
.then(async (release) => {
|
||||
if (release.tag_name.indexOf('nightly') > 0) {
|
||||
const currentBranch = await getCurrentBranch()
|
||||
if (currentBranch === 'master') {
|
||||
npmTag = 'nightly'
|
||||
} else {
|
||||
npmTag = `nightly-${currentBranch}`
|
||||
}
|
||||
} else {
|
||||
npmTag = release.prerelease ? 'beta' : 'latest'
|
||||
}
|
||||
})
|
||||
.then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
|
||||
.then(() => {
|
||||
@@ -115,13 +129,29 @@ new Promise((resolve, reject) => {
|
||||
env: Object.assign({}, process.env, { electron_config_cache: tempDir }),
|
||||
cwd: tempDir
|
||||
})
|
||||
const checkVersion = childProcess.execSync(`${path.join(tempDir, 'node_modules', '.bin', 'electron')} -v`)
|
||||
assert.ok((`v${rootPackageJson.version}`.indexOf(checkVersion.toString().trim()) === 0), `Version is correct`)
|
||||
resolve(tarballPath)
|
||||
})
|
||||
})
|
||||
.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag}`))
|
||||
.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`))
|
||||
.catch((err) => {
|
||||
console.error(`Error: ${err}`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
async function getCurrentBranch () {
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
console.log(`Determining current git branch`)
|
||||
let gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
||||
let branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
let currentBranch = branchDetails.stdout.trim()
|
||||
console.log(`Successfully determined current git branch is ` +
|
||||
`${currentBranch}`)
|
||||
return currentBranch
|
||||
} else {
|
||||
let error = GitProcess.parseError(branchDetails.stderr)
|
||||
console.log(`Could not get details for the current branch,
|
||||
error was ${branchDetails.stderr}`, error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
108
script/release-artifact-cleanup.js
Executable file
108
script/release-artifact-cleanup.js
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
string: ['tag']
|
||||
})
|
||||
const { execSync } = require('child_process')
|
||||
const { GitProcess } = require('dugite')
|
||||
|
||||
const GitHub = require('github')
|
||||
const path = require('path')
|
||||
|
||||
const github = new GitHub()
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
|
||||
github.authenticate({
|
||||
type: 'token',
|
||||
token: process.env.ELECTRON_GITHUB_TOKEN
|
||||
})
|
||||
|
||||
function getLastBumpCommit (tag) {
|
||||
const data = execSync(`git log -n1 --grep "Bump ${tag}" --format='format:{"hash": "%H", "message": "%s"}'`).toString()
|
||||
return JSON.parse(data)
|
||||
}
|
||||
|
||||
async function getCurrentBranch (gitDir) {
|
||||
const gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
||||
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
return branchDetails.stdout.trim()
|
||||
}
|
||||
|
||||
const error = GitProcess.parseError(branchDetails.stderr)
|
||||
console.error(`Couldn't get current branch: `, error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function revertBumpCommit (tag) {
|
||||
const branch = await getCurrentBranch()
|
||||
const commitToRevert = getLastBumpCommit(tag).hash
|
||||
await GitProcess.exec(['revert', commitToRevert], gitDir)
|
||||
const pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], gitDir)
|
||||
if (pushDetails.exitCode === 0) {
|
||||
console.log(`Successfully reverted release commit.`)
|
||||
} else {
|
||||
const error = GitProcess.parseError(pushDetails.stderr)
|
||||
console.error(`Failed to push release commit: `, error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDraft (tag, targetRepo) {
|
||||
try {
|
||||
const result = await github.repos.getReleaseByTag({
|
||||
owner: 'electron',
|
||||
repo: targetRepo,
|
||||
tag
|
||||
})
|
||||
if (!result.draft) {
|
||||
console.log(`Published releases cannot be deleted.`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
await github.repos.deleteRelease({
|
||||
owner: 'electron',
|
||||
repo: targetRepo,
|
||||
release_id: result.id
|
||||
})
|
||||
}
|
||||
console.log(`Successfully deleted draft with tag ${tag} from ${targetRepo}`)
|
||||
} catch (err) {
|
||||
console.error(`Couldn't delete draft with tag ${tag} from ${targetRepo}: `, err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTag (tag, targetRepo) {
|
||||
try {
|
||||
await github.gitdata.deleteReference({
|
||||
owner: 'electron',
|
||||
repo: targetRepo,
|
||||
ref: tag
|
||||
})
|
||||
console.log(`Successfully deleted tag ${tag} from ${targetRepo}`)
|
||||
} catch (err) {
|
||||
console.log(`Couldn't delete tag ${tag} from ${targetRepo}: `, err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanReleaseArtifacts () {
|
||||
const tag = args.tag
|
||||
const isNightly = args.tag.includes('nightly')
|
||||
|
||||
if (isNightly) {
|
||||
await deleteDraft(tag, 'nightlies')
|
||||
await deleteTag(tag, 'nightlies')
|
||||
} else {
|
||||
await deleteDraft(tag, 'electron')
|
||||
}
|
||||
|
||||
await deleteTag(tag, 'electron')
|
||||
await revertBumpCommit(tag)
|
||||
|
||||
console.log('Failed release artifact cleanup complete')
|
||||
}
|
||||
|
||||
cleanReleaseArtifacts()
|
||||
478
script/release-notes/index.js
Normal file
478
script/release-notes/index.js
Normal file
@@ -0,0 +1,478 @@
|
||||
const { GitProcess } = require('dugite')
|
||||
const Entities = require('html-entities').AllHtmlEntities
|
||||
const fetch = require('node-fetch')
|
||||
const fs = require('fs')
|
||||
const GitHub = require('github')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
|
||||
const CACHE_DIR = path.resolve(__dirname, '.cache')
|
||||
// Fill this with tags to ignore if you are generating release notes for older
|
||||
// versions
|
||||
//
|
||||
// E.g. ['v3.0.0-beta.1'] to generate the release notes for 3.0.0-beta.1 :) from
|
||||
// the current 3-0-x branch
|
||||
const EXCLUDE_TAGS = []
|
||||
|
||||
const entities = new Entities()
|
||||
const github = new GitHub()
|
||||
const gitDir = path.resolve(__dirname, '..', '..')
|
||||
github.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })
|
||||
let currentBranch
|
||||
|
||||
const semanticMap = new Map()
|
||||
for (const line of fs.readFileSync(path.resolve(__dirname, 'legacy-pr-semantic-map.csv'), 'utf8').split('\n')) {
|
||||
if (!line) continue
|
||||
const bits = line.split(',')
|
||||
if (bits.length !== 2) continue
|
||||
semanticMap.set(bits[0], bits[1])
|
||||
}
|
||||
|
||||
const getCurrentBranch = async () => {
|
||||
if (currentBranch) return currentBranch
|
||||
const gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
||||
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
currentBranch = branchDetails.stdout.trim()
|
||||
return currentBranch
|
||||
}
|
||||
throw GitProcess.parseError(branchDetails.stderr)
|
||||
}
|
||||
|
||||
const getBranchOffPoint = async (branchName) => {
|
||||
const gitArgs = ['merge-base', branchName, 'master']
|
||||
const commitDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (commitDetails.exitCode === 0) {
|
||||
return commitDetails.stdout.trim()
|
||||
}
|
||||
throw GitProcess.parseError(commitDetails.stderr)
|
||||
}
|
||||
|
||||
const getTagsOnBranch = async (branchName) => {
|
||||
const gitArgs = ['tag', '--merged', branchName]
|
||||
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (tagDetails.exitCode === 0) {
|
||||
return tagDetails.stdout.trim().split('\n').filter(tag => !EXCLUDE_TAGS.includes(tag))
|
||||
}
|
||||
throw GitProcess.parseError(tagDetails.stderr)
|
||||
}
|
||||
|
||||
const memLastKnownRelease = new Map()
|
||||
|
||||
const getLastKnownReleaseOnBranch = async (branchName) => {
|
||||
if (memLastKnownRelease.has(branchName)) {
|
||||
return memLastKnownRelease.get(branchName)
|
||||
}
|
||||
const tags = await getTagsOnBranch(branchName)
|
||||
if (!tags.length) {
|
||||
throw new Error(`Branch ${branchName} has no tags, we have no idea what the last release was`)
|
||||
}
|
||||
const branchOffPointTags = await getTagsOnBranch(await getBranchOffPoint(branchName))
|
||||
if (branchOffPointTags.length >= tags.length) {
|
||||
// No release on this branch
|
||||
return null
|
||||
}
|
||||
memLastKnownRelease.set(branchName, tags[tags.length - 1])
|
||||
// Latest tag is the latest release
|
||||
return tags[tags.length - 1]
|
||||
}
|
||||
|
||||
const getBranches = async () => {
|
||||
const gitArgs = ['branch', '--remote']
|
||||
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
return branchDetails.stdout.trim().split('\n').map(b => b.trim()).filter(branch => branch !== 'origin/HEAD -> origin/master')
|
||||
}
|
||||
throw GitProcess.parseError(branchDetails.stderr)
|
||||
}
|
||||
|
||||
const semverify = (v) => v.replace(/^origin\//, '').replace('x', '0').replace(/-/g, '.')
|
||||
|
||||
const getLastReleaseBranch = async () => {
|
||||
const current = await getCurrentBranch()
|
||||
const allBranches = await getBranches()
|
||||
const releaseBranches = allBranches
|
||||
.filter(branch => /^origin\/[0-9]+-[0-9]+-x$/.test(branch))
|
||||
.filter(branch => branch !== current && branch !== `origin/${current}`)
|
||||
let latest = null
|
||||
for (const b of releaseBranches) {
|
||||
if (latest === null) latest = b
|
||||
if (semver.gt(semverify(b), semverify(latest))) {
|
||||
latest = b
|
||||
}
|
||||
}
|
||||
return latest
|
||||
}
|
||||
|
||||
const commitBeforeTag = async (commit, tag) => {
|
||||
const gitArgs = ['tag', '--contains', commit]
|
||||
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (tagDetails.exitCode === 0) {
|
||||
return tagDetails.stdout.split('\n').includes(tag)
|
||||
}
|
||||
throw GitProcess.parseError(tagDetails.stderr)
|
||||
}
|
||||
|
||||
const getCommitsMergedIntoCurrentBranchSincePoint = async (point) => {
|
||||
return getCommitsBetween(point, 'HEAD')
|
||||
}
|
||||
|
||||
const getCommitsBetween = async (point1, point2) => {
|
||||
const gitArgs = ['rev-list', `${point1}..${point2}`]
|
||||
const commitsDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (commitsDetails.exitCode !== 0) {
|
||||
throw GitProcess.parseError(commitsDetails.stderr)
|
||||
}
|
||||
return commitsDetails.stdout.trim().split('\n')
|
||||
}
|
||||
|
||||
const TITLE_PREFIX = 'Merged Pull Request: '
|
||||
|
||||
const getCommitDetails = async (commitHash) => {
|
||||
const commitInfo = await (await fetch(`https://github.com/electron/electron/branch_commits/${commitHash}`)).text()
|
||||
const bits = commitInfo.split('</a>)')[0].split('>')
|
||||
const prIdent = bits[bits.length - 1].trim()
|
||||
if (!prIdent || commitInfo.indexOf('href="/electron/electron/pull') === -1) {
|
||||
console.warn(`WARNING: Could not track commit "${commitHash}" to a pull request, it may have been committed directly to the branch`)
|
||||
return null
|
||||
}
|
||||
const title = commitInfo.split('title="')[1].split('"')[0]
|
||||
if (!title.startsWith(TITLE_PREFIX)) {
|
||||
console.warn(`WARNING: Unknown PR title for commit "${commitHash}" in PR "${prIdent}"`)
|
||||
return null
|
||||
}
|
||||
return {
|
||||
mergedFrom: prIdent,
|
||||
prTitle: entities.decode(title.substr(TITLE_PREFIX.length))
|
||||
}
|
||||
}
|
||||
|
||||
const doWork = async (items, fn, concurrent = 5) => {
|
||||
const results = []
|
||||
const toUse = [].concat(items)
|
||||
let i = 1
|
||||
const doBit = async () => {
|
||||
if (toUse.length === 0) return
|
||||
console.log(`Running ${i}/${items.length}`)
|
||||
i += 1
|
||||
|
||||
const item = toUse.pop()
|
||||
const index = toUse.length
|
||||
results[index] = await fn(item)
|
||||
await doBit()
|
||||
}
|
||||
const bits = []
|
||||
for (let i = 0; i < concurrent; i += 1) {
|
||||
bits.push(doBit())
|
||||
}
|
||||
await Promise.all(bits)
|
||||
return results
|
||||
}
|
||||
|
||||
const notes = new Map()
|
||||
|
||||
const NoteType = {
|
||||
FIX: 'fix',
|
||||
FEATURE: 'feature',
|
||||
BREAKING_CHANGE: 'breaking-change',
|
||||
DOCUMENTATION: 'doc',
|
||||
OTHER: 'other',
|
||||
UNKNOWN: 'unknown'
|
||||
}
|
||||
|
||||
class Note {
|
||||
constructor (trueTitle, prNumber, ignoreIfInVersion) {
|
||||
// Self bindings
|
||||
this.guessType = this.guessType.bind(this)
|
||||
this.fetchPrInfo = this.fetchPrInfo.bind(this)
|
||||
this._getPr = this._getPr.bind(this)
|
||||
|
||||
if (!trueTitle.trim()) console.error(prNumber)
|
||||
|
||||
this._ignoreIfInVersion = ignoreIfInVersion
|
||||
this.reverted = false
|
||||
if (notes.has(trueTitle)) {
|
||||
console.warn(`Duplicate PR trueTitle: "${trueTitle}", "${prNumber}" this might cause weird reversions (this would be RARE)`)
|
||||
}
|
||||
|
||||
// Memoize
|
||||
notes.set(trueTitle, this)
|
||||
|
||||
this.originalTitle = trueTitle
|
||||
this.title = trueTitle
|
||||
this.prNumber = prNumber
|
||||
this.stripColon = true
|
||||
if (this.guessType() !== NoteType.UNKNOWN && this.stripColon) {
|
||||
this.title = trueTitle.split(':').slice(1).join(':').trim()
|
||||
}
|
||||
}
|
||||
|
||||
guessType () {
|
||||
if (this.originalTitle.startsWith('fix:') ||
|
||||
this.originalTitle.startsWith('Fix:')) return NoteType.FIX
|
||||
if (this.originalTitle.startsWith('feat:')) return NoteType.FEATURE
|
||||
if (this.originalTitle.startsWith('spec:') ||
|
||||
this.originalTitle.startsWith('build:') ||
|
||||
this.originalTitle.startsWith('test:') ||
|
||||
this.originalTitle.startsWith('chore:') ||
|
||||
this.originalTitle.startsWith('deps:') ||
|
||||
this.originalTitle.startsWith('refactor:') ||
|
||||
this.originalTitle.startsWith('tools:') ||
|
||||
this.originalTitle.startsWith('vendor:') ||
|
||||
this.originalTitle.startsWith('perf:') ||
|
||||
this.originalTitle.startsWith('style:') ||
|
||||
this.originalTitle.startsWith('ci')) return NoteType.OTHER
|
||||
if (this.originalTitle.startsWith('doc:') ||
|
||||
this.originalTitle.startsWith('docs:')) return NoteType.DOCUMENTATION
|
||||
|
||||
this.stripColon = false
|
||||
|
||||
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/breaking-change')) {
|
||||
return NoteType.BREAKING_CHANGE
|
||||
}
|
||||
// FIXME: Backported features will not be picked up by this
|
||||
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/nonbreaking-feature')) {
|
||||
return NoteType.FEATURE
|
||||
}
|
||||
|
||||
const n = this.prNumber.replace('#', '')
|
||||
if (semanticMap.has(n)) {
|
||||
switch (semanticMap.get(n)) {
|
||||
case 'feat':
|
||||
return NoteType.FEATURE
|
||||
case 'fix':
|
||||
return NoteType.FIX
|
||||
case 'breaking-change':
|
||||
return NoteType.BREAKING_CHANGE
|
||||
case 'doc':
|
||||
return NoteType.DOCUMENTATION
|
||||
case 'build':
|
||||
case 'vendor':
|
||||
case 'refactor':
|
||||
case 'spec':
|
||||
return NoteType.OTHER
|
||||
default:
|
||||
throw new Error(`Unknown semantic mapping: ${semanticMap.get(n)}`)
|
||||
}
|
||||
}
|
||||
return NoteType.UNKNOWN
|
||||
}
|
||||
|
||||
async _getPr (n) {
|
||||
const cachePath = path.resolve(CACHE_DIR, n)
|
||||
if (fs.existsSync(cachePath)) {
|
||||
return JSON.parse(fs.readFileSync(cachePath, 'utf8'))
|
||||
} else {
|
||||
try {
|
||||
const pr = await github.pullRequests.get({
|
||||
number: n,
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
})
|
||||
fs.writeFileSync(cachePath, JSON.stringify({ data: pr.data }))
|
||||
return pr
|
||||
} catch (err) {
|
||||
console.info('#### FAILED:', `#${n}`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchPrInfo () {
|
||||
if (this.pr) return
|
||||
const n = this.prNumber.replace('#', '')
|
||||
this.pr = await this._getPr(n)
|
||||
if (this.pr.data.labels.find(label => label.name === `merged/${this._ignoreIfInVersion.replace('origin/', '')}`)) {
|
||||
// This means we probably backported this PR, let's try figure out what
|
||||
// the corresponding backport PR would be by searching through comments
|
||||
// for trop
|
||||
let comments
|
||||
const cacheCommentsPath = path.resolve(CACHE_DIR, `${n}-comments`)
|
||||
if (fs.existsSync(cacheCommentsPath)) {
|
||||
comments = JSON.parse(fs.readFileSync(cacheCommentsPath, 'utf8'))
|
||||
} else {
|
||||
comments = await github.issues.getComments({
|
||||
number: n,
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
per_page: 100
|
||||
})
|
||||
fs.writeFileSync(cacheCommentsPath, JSON.stringify({ data: comments.data }))
|
||||
}
|
||||
|
||||
const tropComment = comments.data.find(
|
||||
c => (
|
||||
new RegExp(`We have automatically backported this PR to "${this._ignoreIfInVersion.replace('origin/', '')}", please check out #[0-9]+`)
|
||||
).test(c.body)
|
||||
)
|
||||
|
||||
if (tropComment) {
|
||||
const commentBits = tropComment.body.split('#')
|
||||
const tropPrNumber = commentBits[commentBits.length - 1]
|
||||
|
||||
const tropPr = await this._getPr(tropPrNumber)
|
||||
if (tropPr.data.merged && tropPr.data.merge_commit_sha) {
|
||||
if (await commitBeforeTag(tropPr.data.merge_commit_sha, await getLastKnownReleaseOnBranch(this._ignoreIfInVersion))) {
|
||||
this.reverted = true
|
||||
console.log('PR', this.prNumber, 'was backported to a previous version, ignoring from notes')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Note.findByTrueTitle = (trueTitle) => notes.get(trueTitle)
|
||||
|
||||
class ReleaseNotes {
|
||||
constructor (ignoreIfInVersion) {
|
||||
this._ignoreIfInVersion = ignoreIfInVersion
|
||||
this._handledPrs = new Set()
|
||||
this._revertedPrs = new Set()
|
||||
this.other = []
|
||||
this.docs = []
|
||||
this.fixes = []
|
||||
this.features = []
|
||||
this.breakingChanges = []
|
||||
this.unknown = []
|
||||
}
|
||||
|
||||
async parseCommits (commitHashes) {
|
||||
await doWork(commitHashes, async (commit) => {
|
||||
const info = await getCommitDetails(commit)
|
||||
if (!info) return
|
||||
// Only handle each PR once
|
||||
if (this._handledPrs.has(info.mergedFrom)) return
|
||||
this._handledPrs.add(info.mergedFrom)
|
||||
|
||||
// Strip the trop backport prefix
|
||||
const trueTitle = info.prTitle.replace(/^Backport \([0-9]+-[0-9]+-x\) - /, '')
|
||||
if (this._revertedPrs.has(trueTitle)) return
|
||||
|
||||
// Handle PRs that revert other PRs
|
||||
if (trueTitle.startsWith('Revert "')) {
|
||||
const revertedTrueTitle = trueTitle.substr(8, trueTitle.length - 9)
|
||||
this._revertedPrs.add(revertedTrueTitle)
|
||||
const existingNote = Note.findByTrueTitle(revertedTrueTitle)
|
||||
if (existingNote) {
|
||||
existingNote.reverted = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a note for this PR
|
||||
const note = new Note(trueTitle, info.mergedFrom, this._ignoreIfInVersion)
|
||||
try {
|
||||
await note.fetchPrInfo()
|
||||
} catch (err) {
|
||||
console.error(commit, info)
|
||||
throw err
|
||||
}
|
||||
switch (note.guessType()) {
|
||||
case NoteType.FIX:
|
||||
this.fixes.push(note)
|
||||
break
|
||||
case NoteType.FEATURE:
|
||||
this.features.push(note)
|
||||
break
|
||||
case NoteType.BREAKING_CHANGE:
|
||||
this.breakingChanges.push(note)
|
||||
break
|
||||
case NoteType.OTHER:
|
||||
this.other.push(note)
|
||||
break
|
||||
case NoteType.DOCUMENTATION:
|
||||
this.docs.push(note)
|
||||
break
|
||||
case NoteType.UNKNOWN:
|
||||
default:
|
||||
this.unknown.push(note)
|
||||
break
|
||||
}
|
||||
}, 20)
|
||||
}
|
||||
|
||||
list (notes) {
|
||||
if (notes.length === 0) {
|
||||
return '_There are no items in this section this release_'
|
||||
}
|
||||
return notes
|
||||
.filter(note => !note.reverted)
|
||||
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()))
|
||||
.map((note) => `* ${note.title.trim()} ${note.prNumber}`).join('\n')
|
||||
}
|
||||
|
||||
render () {
|
||||
return `
|
||||
# Release Notes
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
${this.list(this.breakingChanges)}
|
||||
|
||||
## Features
|
||||
|
||||
${this.list(this.features)}
|
||||
|
||||
## Fixes
|
||||
|
||||
${this.list(this.fixes)}
|
||||
|
||||
## Other Changes (E.g. Internal refactors or build system updates)
|
||||
|
||||
${this.list(this.other)}
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
Some documentation updates, fixes and reworks: ${
|
||||
this.docs.length === 0
|
||||
? '_None in this release_'
|
||||
: this.docs.sort((a, b) => a.prNumber.localeCompare(b.prNumber)).map(note => note.prNumber).join(', ')
|
||||
}
|
||||
${this.unknown.filter(n => !n.reverted).length > 0
|
||||
? `## Unknown (fix these before publishing release)
|
||||
|
||||
${this.list(this.unknown)}
|
||||
` : ''}`
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
if (!fs.existsSync(CACHE_DIR)) {
|
||||
fs.mkdirSync(CACHE_DIR)
|
||||
}
|
||||
const lastReleaseBranch = await getLastReleaseBranch()
|
||||
|
||||
const notes = new ReleaseNotes(lastReleaseBranch)
|
||||
const lastKnownReleaseInCurrentStream = await getLastKnownReleaseOnBranch(await getCurrentBranch())
|
||||
const currentBranchOff = await getBranchOffPoint(await getCurrentBranch())
|
||||
|
||||
const commits = await getCommitsMergedIntoCurrentBranchSincePoint(
|
||||
lastKnownReleaseInCurrentStream || currentBranchOff
|
||||
)
|
||||
|
||||
if (!lastKnownReleaseInCurrentStream) {
|
||||
// This means we are the first release in our stream
|
||||
// FIXME: This will not work for minor releases!!!!
|
||||
|
||||
const lastReleaseBranch = await getLastReleaseBranch()
|
||||
const lastBranchOff = await getBranchOffPoint(lastReleaseBranch)
|
||||
commits.push(...await getCommitsBetween(lastBranchOff, currentBranchOff))
|
||||
}
|
||||
|
||||
await notes.parseCommits(commits)
|
||||
|
||||
console.log(notes.render())
|
||||
|
||||
const badNotes = notes.unknown.filter(n => !n.reverted).length
|
||||
if (badNotes > 0) {
|
||||
throw new Error(`You have ${badNotes.length} unknown release notes, please fix them before releasing`)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.mainModule === module) {
|
||||
main().catch((err) => {
|
||||
console.error('Error Occurred:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
@@ -0,0 +1,193 @@
|
||||
12884,fix
|
||||
12093,feat
|
||||
12595,doc
|
||||
12674,doc
|
||||
12577,doc
|
||||
12084,doc
|
||||
12103,doc
|
||||
12948,build
|
||||
12496,feat
|
||||
13133,build
|
||||
12651,build
|
||||
12767,doc
|
||||
12238,build
|
||||
12646,build
|
||||
12373,doc
|
||||
12723,feat
|
||||
12202,doc
|
||||
12504,doc
|
||||
12669,doc
|
||||
13044,feat
|
||||
12746,spec
|
||||
12617,doc
|
||||
12532,feat
|
||||
12619,feat
|
||||
12118,build
|
||||
12921,build
|
||||
13281,doc
|
||||
12059,feat
|
||||
12131,doc
|
||||
12123,doc
|
||||
12080,build
|
||||
12904,fix
|
||||
12562,fix
|
||||
12122,spec
|
||||
12817,spec
|
||||
12254,fix
|
||||
12999,vendor
|
||||
13248,vendor
|
||||
12104,build
|
||||
12477,feat
|
||||
12648,refactor
|
||||
12649,refactor
|
||||
12650,refactor
|
||||
12673,refactor
|
||||
12305,refactor
|
||||
12168,refactor
|
||||
12627,refactor
|
||||
12446,doc
|
||||
12304,refactor
|
||||
12615,breaking-change
|
||||
12135,feat
|
||||
12155,doc
|
||||
12975,fix
|
||||
12501,fix
|
||||
13065,fix
|
||||
13089,build
|
||||
12786,doc
|
||||
12736,doc
|
||||
11966,doc
|
||||
12885,fix
|
||||
12984,refactor
|
||||
12187,build
|
||||
12535,refactor
|
||||
12538,feat
|
||||
12190,fix
|
||||
12139,fix
|
||||
11328,fix
|
||||
12828,feat
|
||||
12614,feat
|
||||
12546,feat
|
||||
12647,refactor
|
||||
12987,build
|
||||
12900,doc
|
||||
12389,doc
|
||||
12387,doc
|
||||
12232,doc
|
||||
12742,build
|
||||
12043,fix
|
||||
12741,fix
|
||||
12995,fix
|
||||
12395,fix
|
||||
12003,build
|
||||
12216,fix
|
||||
12132,fix
|
||||
12062,fix
|
||||
12968,doc
|
||||
12422,doc
|
||||
12149,doc
|
||||
13339,build
|
||||
12044,fix
|
||||
12327,fix
|
||||
12180,fix
|
||||
12263,spec
|
||||
12153,spec
|
||||
13055,feat
|
||||
12113,doc
|
||||
12067,doc
|
||||
12882,build
|
||||
13029,build
|
||||
13067,doc
|
||||
12196,build
|
||||
12797,doc
|
||||
12013,fix
|
||||
12507,fix
|
||||
11607,feat
|
||||
12837,build
|
||||
11613,feat
|
||||
12015,spec
|
||||
12058,doc
|
||||
12403,spec
|
||||
12192,feat
|
||||
12204,doc
|
||||
13294,doc
|
||||
12542,doc
|
||||
12826,refactor
|
||||
12781,doc
|
||||
12157,fix
|
||||
12319,fix
|
||||
12188,build
|
||||
12399,doc
|
||||
12145,doc
|
||||
12661,refactor
|
||||
8953,fix
|
||||
12037,fix
|
||||
12186,spec
|
||||
12397,fix
|
||||
12040,doc
|
||||
12886,refactor
|
||||
12008,refactor
|
||||
12716,refactor
|
||||
12750,refactor
|
||||
12787,refactor
|
||||
12858,refactor
|
||||
12140,refactor
|
||||
12503,refactor
|
||||
12514,refactor
|
||||
12584,refactor
|
||||
12596,refactor
|
||||
12637,refactor
|
||||
12660,refactor
|
||||
12696,refactor
|
||||
12877,refactor
|
||||
13030,refactor
|
||||
12916,build
|
||||
12896,build
|
||||
13039,breaking-change
|
||||
11927,build
|
||||
12847,doc
|
||||
12852,doc
|
||||
12194,fix
|
||||
12870,doc
|
||||
12924,fix
|
||||
12682,doc
|
||||
12004,refactor
|
||||
12601,refactor
|
||||
12998,fix
|
||||
13105,vendor
|
||||
12452,doc
|
||||
12738,fix
|
||||
12536,refactor
|
||||
12189,spec
|
||||
13122,spec
|
||||
12662,fix
|
||||
12665,doc
|
||||
12419,feat
|
||||
12756,doc
|
||||
12616,refactor
|
||||
12679,breaking-change
|
||||
12000,doc
|
||||
12372,build
|
||||
12805,build
|
||||
12348,fix
|
||||
12315,doc
|
||||
12072,doc
|
||||
12912,doc
|
||||
12982,fix
|
||||
12105,doc
|
||||
12917,spec
|
||||
12400,doc
|
||||
12101,feat
|
||||
12642,build
|
||||
13058,fix
|
||||
12913,vendor
|
||||
13298,vendor
|
||||
13042,build
|
||||
11230,feat
|
||||
11459,feat
|
||||
12476,vendor
|
||||
11937,doc
|
||||
12328,build
|
||||
12539,refactor
|
||||
12127,build
|
||||
12537,build
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const args = require('minimist')(process.argv.slice(2))
|
||||
const assert = require('assert')
|
||||
const fs = require('fs')
|
||||
const { execSync } = require('child_process')
|
||||
const GitHub = require('github')
|
||||
@@ -16,17 +16,16 @@ const fail = '\u2717'.red
|
||||
const sumchecker = require('sumchecker')
|
||||
const temp = require('temp').track()
|
||||
const { URL } = require('url')
|
||||
const targetRepo = pkgVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
let failureCount = 0
|
||||
|
||||
assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
|
||||
|
||||
const github = new GitHub({
|
||||
followRedirects: false
|
||||
})
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
|
||||
async function getDraftRelease (version, skipValidation) {
|
||||
let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: 'electron'})
|
||||
let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: targetRepo})
|
||||
let drafts
|
||||
let versionToCheck
|
||||
if (version) {
|
||||
@@ -90,15 +89,12 @@ function assetsForVersion (version, validatingRelease) {
|
||||
`electron-${version}-darwin-x64-dsym.zip`,
|
||||
`electron-${version}-darwin-x64-symbols.zip`,
|
||||
`electron-${version}-darwin-x64.zip`,
|
||||
`electron-${version}-linux-arm-symbols.zip`,
|
||||
`electron-${version}-linux-arm.zip`,
|
||||
`electron-${version}-linux-arm64-symbols.zip`,
|
||||
`electron-${version}-linux-arm64.zip`,
|
||||
`electron-${version}-linux-armv7l-symbols.zip`,
|
||||
`electron-${version}-linux-armv7l.zip`,
|
||||
`electron-${version}-linux-ia32-symbols.zip`,
|
||||
`electron-${version}-linux-ia32.zip`,
|
||||
// `electron-${version}-linux-mips64el.zip`,
|
||||
`electron-${version}-linux-x64-symbols.zip`,
|
||||
`electron-${version}-linux-x64.zip`,
|
||||
`electron-${version}-mas-x64-dsym.zip`,
|
||||
@@ -113,11 +109,9 @@ function assetsForVersion (version, validatingRelease) {
|
||||
`electron-api.json`,
|
||||
`electron.d.ts`,
|
||||
`ffmpeg-${version}-darwin-x64.zip`,
|
||||
`ffmpeg-${version}-linux-arm.zip`,
|
||||
`ffmpeg-${version}-linux-arm64.zip`,
|
||||
`ffmpeg-${version}-linux-armv7l.zip`,
|
||||
`ffmpeg-${version}-linux-ia32.zip`,
|
||||
// `ffmpeg-${version}-linux-mips64el.zip`,
|
||||
`ffmpeg-${version}-linux-x64.zip`,
|
||||
`ffmpeg-${version}-mas-x64.zip`,
|
||||
`ffmpeg-${version}-win32-ia32.zip`,
|
||||
@@ -147,6 +141,8 @@ function s3UrlsForVersion (version) {
|
||||
}
|
||||
|
||||
function checkVersion () {
|
||||
if (args.skipVersionCheck) return
|
||||
|
||||
console.log(`Verifying that app version matches package version ${pkgVersion}.`)
|
||||
let startScript = path.join(__dirname, 'start.py')
|
||||
let scriptArgs = ['--version']
|
||||
@@ -184,11 +180,7 @@ function uploadNodeShasums () {
|
||||
function uploadIndexJson () {
|
||||
console.log('Uploading index.json to S3.')
|
||||
let scriptPath = path.join(__dirname, 'upload-index-json.py')
|
||||
let scriptArgs = []
|
||||
if (args.automaticRelease) {
|
||||
scriptArgs.push('-R')
|
||||
}
|
||||
runScript(scriptPath, scriptArgs)
|
||||
runScript(scriptPath, [pkgVersion])
|
||||
console.log(`${pass} Done uploading index.json to S3.`)
|
||||
}
|
||||
|
||||
@@ -199,7 +191,7 @@ async function createReleaseShasums (release) {
|
||||
console.log(`${fileName} already exists on GitHub; deleting before creating new file.`)
|
||||
await github.repos.deleteAsset({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: existingAssets[0].id
|
||||
}).catch(err => {
|
||||
console.log(`${fail} Error deleting ${fileName} on GitHub:`, err)
|
||||
@@ -218,7 +210,7 @@ async function createReleaseShasums (release) {
|
||||
async function uploadShasumFile (filePath, fileName, release) {
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: release.id,
|
||||
filePath,
|
||||
name: fileName
|
||||
@@ -253,7 +245,7 @@ function saveShaSumFile (checksums, fileName) {
|
||||
async function publishRelease (release) {
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: release.id,
|
||||
tag_name: release.tag_name,
|
||||
draft: false
|
||||
@@ -280,6 +272,7 @@ async function makeRelease (releaseToValidate) {
|
||||
let draftRelease = await getDraftRelease()
|
||||
uploadNodeShasums()
|
||||
uploadIndexJson()
|
||||
|
||||
await createReleaseShasums(draftRelease)
|
||||
// Fetch latest version of release before verifying
|
||||
draftRelease = await getDraftRelease(pkgVersion, true)
|
||||
@@ -307,7 +300,7 @@ async function verifyAssets (release) {
|
||||
let downloadDir = await makeTempDir()
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
headers: {
|
||||
Accept: 'application/octet-stream'
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
# Do NOT CHANGE this if you don't know what you're doing -- see
|
||||
# https://code.google.com/p/chromium/wiki/UpdatingClang
|
||||
# Reverting problematic clang rolls is safe, though.
|
||||
CLANG_REVISION=307486
|
||||
CLANG_REVISION=308728
|
||||
|
||||
# This is incremented when pushing a new build of Clang at the same revision.
|
||||
CLANG_SUB_REVISION=1
|
||||
CLANG_SUB_REVISION=3
|
||||
|
||||
PACKAGE_VERSION="${CLANG_REVISION}-${CLANG_SUB_REVISION}"
|
||||
|
||||
|
||||
@@ -2,39 +2,49 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
from lib.config import PLATFORM, s3_config
|
||||
from lib.util import electron_gyp, execute, s3put, scoped_cwd
|
||||
|
||||
from lib.config import s3_config
|
||||
from lib.util import s3put, scoped_cwd, safe_mkdir
|
||||
|
||||
SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'D')
|
||||
|
||||
PROJECT_NAME = electron_gyp()['project_name%']
|
||||
PRODUCT_NAME = electron_gyp()['product_name%']
|
||||
BASE_URL = 'https://electron-metadumper.herokuapp.com/?version='
|
||||
|
||||
version = sys.argv[1]
|
||||
authToken = os.getenv('META_DUMPER_AUTH_HEADER')
|
||||
|
||||
def get_content(retry_count = 5):
|
||||
try:
|
||||
request = urllib2.Request(
|
||||
BASE_URL + version,
|
||||
headers={"Authorization" : authToken}
|
||||
)
|
||||
|
||||
return urllib2.urlopen(
|
||||
request
|
||||
).read()
|
||||
except Exception as e:
|
||||
if retry_count == 0:
|
||||
raise e
|
||||
return get_content(retry_count - 1)
|
||||
|
||||
def main():
|
||||
if not authToken or authToken == "":
|
||||
raise Exception("Please set META_DUMPER_AUTH_HEADER")
|
||||
# Upload the index.json.
|
||||
with scoped_cwd(SOURCE_ROOT):
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '-R':
|
||||
config = 'R'
|
||||
else:
|
||||
config = 'D'
|
||||
out_dir = os.path.join(SOURCE_ROOT, 'out', config)
|
||||
if sys.platform == 'darwin':
|
||||
electron = os.path.join(out_dir, '{0}.app'.format(PRODUCT_NAME),
|
||||
'Contents', 'MacOS', PRODUCT_NAME)
|
||||
elif sys.platform == 'win32':
|
||||
electron = os.path.join(out_dir, '{0}.exe'.format(PROJECT_NAME))
|
||||
else:
|
||||
electron = os.path.join(out_dir, PROJECT_NAME)
|
||||
index_json = os.path.relpath(os.path.join(out_dir, 'index.json'))
|
||||
execute([electron,
|
||||
os.path.join('tools', 'dump-version-info.js'),
|
||||
index_json])
|
||||
safe_mkdir(OUT_DIR)
|
||||
index_json = os.path.relpath(os.path.join(OUT_DIR, 'index.json'))
|
||||
|
||||
new_content = get_content()
|
||||
|
||||
with open(index_json, "w") as f:
|
||||
f.write(new_content)
|
||||
|
||||
bucket, access_key, secret_key = s3_config()
|
||||
s3put(bucket, access_key, secret_key, out_dir, 'atom-shell/dist',
|
||||
s3put(bucket, access_key, secret_key, OUT_DIR, 'atom-shell/dist',
|
||||
[index_json])
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
|
||||
const GitHub = require('github')
|
||||
const github = new GitHub()
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
|
||||
if (process.argv.length < 5) {
|
||||
if (process.argv.length < 6) {
|
||||
console.log('Usage: upload-to-github filePath fileName releaseId')
|
||||
process.exit(1)
|
||||
}
|
||||
let filePath = process.argv[2]
|
||||
let fileName = process.argv[3]
|
||||
let releaseId = process.argv[4]
|
||||
let releaseVersion = process.argv[5]
|
||||
|
||||
const targetRepo = releaseVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: releaseId,
|
||||
filePath: filePath,
|
||||
name: fileName
|
||||
@@ -34,7 +39,7 @@ function uploadToGitHub () {
|
||||
console.log(`${fileName} already exists; will delete before retrying upload.`)
|
||||
github.repos.deleteAsset({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: existingAssets[0].id
|
||||
}).then(uploadToGitHub).catch(uploadToGitHub)
|
||||
} else {
|
||||
|
||||
@@ -166,7 +166,7 @@ def upload_electron(release, file_path, args):
|
||||
return
|
||||
|
||||
# Upload the file.
|
||||
upload_io_to_github(release, filename, file_path)
|
||||
upload_io_to_github(release, filename, file_path, args.version)
|
||||
|
||||
# Upload the checksum file.
|
||||
upload_sha256_checksum(args.version, file_path)
|
||||
@@ -180,11 +180,12 @@ def upload_electron(release, file_path, args):
|
||||
upload_electron(release, arm_file_path, args)
|
||||
|
||||
|
||||
def upload_io_to_github(release, filename, filepath):
|
||||
def upload_io_to_github(release, filename, filepath, version):
|
||||
print 'Uploading %s to Github' % \
|
||||
(filename)
|
||||
script_path = os.path.join(SOURCE_ROOT, 'script', 'upload-to-github.js')
|
||||
execute(['node', script_path, filepath, filename, str(release['id'])])
|
||||
execute(['node', script_path, filepath, filename, str(release['id']),
|
||||
version])
|
||||
|
||||
|
||||
def upload_sha256_checksum(version, file_path, key_prefix=None):
|
||||
|
||||
@@ -417,31 +417,13 @@ describe('app module', () => {
|
||||
app.setLoginItemSettings({openAtLogin: false, path: updateExe, args: processStartArgs})
|
||||
})
|
||||
|
||||
it('returns the login item status of the app', (done) => {
|
||||
app.setLoginItemSettings({openAtLogin: true})
|
||||
assert.deepEqual(app.getLoginItemSettings(), {
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false
|
||||
})
|
||||
|
||||
app.setLoginItemSettings({openAtLogin: true, openAsHidden: true})
|
||||
assert.deepEqual(app.getLoginItemSettings(), {
|
||||
openAtLogin: true,
|
||||
openAsHidden: process.platform === 'darwin' && !process.mas, // Only available on macOS
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false
|
||||
})
|
||||
|
||||
app.setLoginItemSettings({})
|
||||
it('sets and returns the app as a login item', done => {
|
||||
app.setLoginItemSettings({ openAtLogin: true })
|
||||
// Wait because login item settings are not applied immediately in MAS build
|
||||
const delay = process.mas ? 100 : 0
|
||||
const delay = process.mas ? 150 : 0
|
||||
setTimeout(() => {
|
||||
assert.deepEqual(app.getLoginItemSettings(), {
|
||||
openAtLogin: false,
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
@@ -451,6 +433,32 @@ describe('app module', () => {
|
||||
}, delay)
|
||||
})
|
||||
|
||||
it('adds a login item that loads in hidden mode', () => {
|
||||
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true })
|
||||
assert.deepEqual(app.getLoginItemSettings(), {
|
||||
openAtLogin: true,
|
||||
openAsHidden: process.platform === 'darwin' && !process.mas, // Only available on macOS
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false
|
||||
})
|
||||
})
|
||||
|
||||
it('correctly sets and unsets the LoginItem as hidden', function () {
|
||||
if (process.platform !== 'darwin' || process.mas) this.skip()
|
||||
|
||||
assert.equal(app.getLoginItemSettings().openAtLogin, false)
|
||||
assert.equal(app.getLoginItemSettings().openAsHidden, false)
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true })
|
||||
assert.equal(app.getLoginItemSettings().openAtLogin, true)
|
||||
assert.equal(app.getLoginItemSettings().openAsHidden, true)
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false })
|
||||
assert.equal(app.getLoginItemSettings().openAtLogin, true)
|
||||
assert.equal(app.getLoginItemSettings().openAsHidden, false)
|
||||
})
|
||||
|
||||
it('allows you to pass a custom executable and arguments', function () {
|
||||
if (process.platform !== 'win32') {
|
||||
// FIXME(alexeykuzmin): Skip the test.
|
||||
|
||||
@@ -2349,9 +2349,15 @@ describe('BrowserWindow module', () => {
|
||||
// Only implemented on windows.
|
||||
if (process.platform !== 'win32') return
|
||||
|
||||
it('is set to false when resizable state is set to false', () => {
|
||||
it('is reset to its former state', () => {
|
||||
w.setMaximizable(false)
|
||||
w.setResizable(false)
|
||||
w.setResizable(true)
|
||||
assert.equal(w.isMaximizable(), false)
|
||||
w.setMaximizable(true)
|
||||
w.setResizable(false)
|
||||
w.setResizable(true)
|
||||
assert.strictEqual(w.isMaximizable(), true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
25
spec/api-tray-spec.js
Normal file
25
spec/api-tray-spec.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const {remote} = require('electron')
|
||||
const {Menu, Tray, nativeImage} = remote
|
||||
|
||||
describe('tray module', () => {
|
||||
describe('tray.setContextMenu', () => {
|
||||
let tray
|
||||
|
||||
beforeEach(() => {
|
||||
tray = new Tray(nativeImage.createEmpty())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
tray.destroy()
|
||||
tray = null
|
||||
})
|
||||
|
||||
it('accepts menu instance', () => {
|
||||
tray.setContextMenu(new Menu())
|
||||
})
|
||||
|
||||
it('accepts null', () => {
|
||||
tray.setContextMenu(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,8 @@ const assert = require('assert')
|
||||
const ChildProcess = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const temp = require('temp').track()
|
||||
const util = require('util')
|
||||
const {closeWindow} = require('./window-helpers')
|
||||
|
||||
const nativeImage = require('electron').nativeImage
|
||||
@@ -135,6 +137,44 @@ describe('asar package', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.copyFile', function () {
|
||||
it('copies a normal file', function (done) {
|
||||
const p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
const dest = temp.path()
|
||||
fs.copyFile(p, dest, function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
assert(fs.readFileSync(p).equals(fs.readFileSync(dest)))
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('copies a unpacked file', function (done) {
|
||||
const p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt')
|
||||
const dest = temp.path()
|
||||
fs.copyFile(p, dest, function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
assert(fs.readFileSync(p).equals(fs.readFileSync(dest)))
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.copyFileSync', function () {
|
||||
it('copies a normal file', function () {
|
||||
const p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
const dest = temp.path()
|
||||
fs.copyFileSync(p, dest)
|
||||
assert(fs.readFileSync(p).equals(fs.readFileSync(dest)))
|
||||
})
|
||||
|
||||
it('copies a unpacked file', function () {
|
||||
const p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt')
|
||||
const dest = temp.path()
|
||||
fs.copyFileSync(p, dest)
|
||||
assert(fs.readFileSync(p).equals(fs.readFileSync(dest)))
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.lstatSync', function () {
|
||||
it('handles path with trailing slash correctly', function () {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1')
|
||||
@@ -549,6 +589,60 @@ describe('asar package', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.exists', function () {
|
||||
it('handles an existing file', function (done) {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
// eslint-disable-next-line
|
||||
fs.exists(p, function (exists) {
|
||||
assert.equal(exists, true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles a non-existent file', function (done) {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist')
|
||||
// eslint-disable-next-line
|
||||
fs.exists(p, function (exists) {
|
||||
assert.equal(exists, false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('promisified version handles an existing file', (done) => {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
// eslint-disable-next-line
|
||||
util.promisify(fs.exists)(p).then(exists => {
|
||||
assert.equal(exists, true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('promisified version handles a non-existent file', function (done) {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist')
|
||||
// eslint-disable-next-line
|
||||
util.promisify(fs.exists)(p).then(exists => {
|
||||
assert.equal(exists, false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.existsSync', function () {
|
||||
it('handles an existing file', function () {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
assert.doesNotThrow(function () {
|
||||
assert.equal(fs.existsSync(p), true)
|
||||
})
|
||||
})
|
||||
|
||||
it('handles a non-existent file', function () {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist')
|
||||
assert.doesNotThrow(function () {
|
||||
assert.equal(fs.existsSync(p), false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fs.access', function () {
|
||||
it('accesses a normal file', function (done) {
|
||||
var p = path.join(fixtures, 'asar', 'a.asar', 'file1')
|
||||
@@ -644,6 +738,12 @@ describe('asar package', function () {
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can be promisified', () => {
|
||||
return util.promisify(ChildProcess.exec)('echo ' + echo + ' foo bar').then(({ stdout }) => {
|
||||
assert.equal(stdout.toString().replace(/\r/g, ''), echo + ' foo bar\n')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('child_process.execSync', function () {
|
||||
@@ -680,6 +780,12 @@ describe('asar package', function () {
|
||||
var output = execFileSync(echo, ['test'])
|
||||
assert.equal(String(output), 'test\n')
|
||||
})
|
||||
|
||||
it('can be promisified', () => {
|
||||
return util.promisify(ChildProcess.execFile)(echo, ['test']).then(({ stdout }) => {
|
||||
assert.equal(stdout, 'test\n')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('internalModuleReadFile', function () {
|
||||
@@ -700,6 +806,18 @@ describe('asar package', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('util.promisify', function () {
|
||||
it('can promisify all fs functions', function () {
|
||||
const originalFs = require('original-fs')
|
||||
|
||||
for (const key in originalFs) {
|
||||
if (originalFs[key][util.promisify.custom] && !fs[key][util.promisify.custom]) {
|
||||
assert(false, `fs.${key}[util.promisify.custom] missing`)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('process.noAsar', function () {
|
||||
var errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR'
|
||||
|
||||
|
||||
63
spec/fixtures/api/mixed-sandbox-app/main.js
vendored
63
spec/fixtures/api/mixed-sandbox-app/main.js
vendored
@@ -10,27 +10,32 @@ if (!process.argv.includes('--enable-mixed-sandbox')) {
|
||||
app.enableMixedSandbox()
|
||||
}
|
||||
|
||||
let sandboxWindow
|
||||
let noSandboxWindow
|
||||
let currentWindowSandboxed = false
|
||||
|
||||
app.once('ready', () => {
|
||||
sandboxWindow = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'electron-app-mixed-sandbox-preload.js'),
|
||||
sandbox: true
|
||||
}
|
||||
})
|
||||
sandboxWindow.loadURL('about:blank')
|
||||
|
||||
noSandboxWindow = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'electron-app-mixed-sandbox-preload.js'),
|
||||
sandbox: false
|
||||
}
|
||||
})
|
||||
noSandboxWindow.loadURL('about:blank')
|
||||
function testWindow (isSandboxed, callback) {
|
||||
currentWindowSandboxed = isSandboxed
|
||||
let currentWindow = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'electron-app-mixed-sandbox-preload.js'),
|
||||
sandbox: isSandboxed
|
||||
}
|
||||
})
|
||||
currentWindow.loadURL('about:blank')
|
||||
currentWindow.webContents.once('devtools-opened', () => {
|
||||
if (isSandboxed) {
|
||||
argv.sandboxDevtools = true
|
||||
} else {
|
||||
argv.noSandboxDevtools = true
|
||||
}
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
finish()
|
||||
})
|
||||
currentWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
const argv = {
|
||||
sandbox: null,
|
||||
@@ -41,6 +46,10 @@ app.once('ready', () => {
|
||||
|
||||
let connected = false
|
||||
|
||||
testWindow(true, () => {
|
||||
testWindow()
|
||||
})
|
||||
|
||||
function finish () {
|
||||
if (connected && argv.sandbox != null && argv.noSandbox != null &&
|
||||
argv.noSandboxDevtools != null && argv.sandboxDevtools != null) {
|
||||
@@ -57,22 +66,10 @@ app.once('ready', () => {
|
||||
finish()
|
||||
})
|
||||
|
||||
noSandboxWindow.webContents.once('devtools-opened', () => {
|
||||
argv.noSandboxDevtools = true
|
||||
finish()
|
||||
})
|
||||
noSandboxWindow.webContents.openDevTools()
|
||||
|
||||
sandboxWindow.webContents.once('devtools-opened', () => {
|
||||
argv.sandboxDevtools = true
|
||||
finish()
|
||||
})
|
||||
sandboxWindow.webContents.openDevTools()
|
||||
|
||||
ipcMain.on('argv', (event, value) => {
|
||||
if (event.sender === sandboxWindow.webContents) {
|
||||
if (currentWindowSandboxed) {
|
||||
argv.sandbox = value
|
||||
} else if (event.sender === noSandboxWindow.webContents) {
|
||||
} else {
|
||||
argv.noSandbox = value
|
||||
}
|
||||
finish()
|
||||
|
||||
2
vendor/libchromiumcontent
vendored
2
vendor/libchromiumcontent
vendored
Submodule vendor/libchromiumcontent updated: 5caba227fd...ccdb085454
2
vendor/node
vendored
2
vendor/node
vendored
Submodule vendor/node updated: f2bcc6b582...51abeb37ca
69
vsts.yml
69
vsts.yml
@@ -2,29 +2,21 @@ resources:
|
||||
- repo: self
|
||||
steps:
|
||||
- bash: |
|
||||
if [ "$ELECTRON_RELEASE" == "1" ]; then
|
||||
echo 'Bootstrapping Electron for release build'
|
||||
script/bootstrap.py --target_arch=$TARGET_ARCH
|
||||
else
|
||||
echo 'Bootstrapping Electron for debug build'
|
||||
script/bootstrap.py --target_arch=$TARGET_ARCH --dev
|
||||
fi
|
||||
echo 'Non release VSTS builds do not run on older branches'
|
||||
displayName: Skip build on older branch
|
||||
condition: ne(variables['ELECTRON_RELEASE'], '1')
|
||||
|
||||
- bash: |
|
||||
echo 'Bootstrapping Electron for release build'
|
||||
script/bootstrap.py --target_arch=$TARGET_ARCH
|
||||
name: Bootstrap
|
||||
condition: eq(variables['ELECTRON_RELEASE'], '1')
|
||||
|
||||
- bash: |
|
||||
npm run lint
|
||||
name: Lint
|
||||
condition: and(succeeded(), ne(variables['ELECTRON_RELEASE'], '1'))
|
||||
|
||||
- bash: |
|
||||
if [ "$ELECTRON_RELEASE" == "1" ]; then
|
||||
echo 'Building Electron for release'
|
||||
script/build.py -c R
|
||||
else
|
||||
echo 'Building Electron for debug'
|
||||
script/build.py -c D
|
||||
fi
|
||||
echo 'Building Electron for release'
|
||||
script/build.py -c R
|
||||
name: Build
|
||||
condition: and(succeeded(), eq(variables['ELECTRON_RELEASE'], '1'))
|
||||
|
||||
- bash: |
|
||||
echo 'Creating Electron release distribution'
|
||||
@@ -43,40 +35,11 @@ steps:
|
||||
name: Upload_distribution
|
||||
condition: and(succeeded(), eq(variables['ELECTRON_RELEASE'], '1'))
|
||||
|
||||
- bash: |
|
||||
echo 'Testing Electron build'
|
||||
mkdir junit
|
||||
export MOCHA_FILE="junit/test-results.xml"
|
||||
export MOCHA_REPORTER="mocha-junit-reporter"
|
||||
if [ "$ELECTRON_RELEASE" == "1" ]; then
|
||||
script/test.py --ci --rebuild_native_modules -c R
|
||||
else
|
||||
script/test.py --ci --rebuild_native_modules
|
||||
fi
|
||||
name: Test
|
||||
|
||||
- bash: |
|
||||
echo 'Verifying ffmpeg on build'
|
||||
if [ "$ELECTRON_RELEASE" == "1" ]; then
|
||||
script/verify-ffmpeg.py -R
|
||||
else
|
||||
script/verify-ffmpeg.py
|
||||
fi
|
||||
name: Verify_FFmpeg
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Publish Build Artifacts
|
||||
inputs:
|
||||
testResultsFiles: 'test-results.xml'
|
||||
searchFolder: junit
|
||||
condition: and(always(), ne(variables['ELECTRON_RELEASE'], '1'))
|
||||
|
||||
- task: kasunkodagoda.slack-notification.slack-notification-task.SlackNotification@3
|
||||
displayName: Post Slack Notification
|
||||
inputs:
|
||||
SlackApiToken: '$(slack_token)'
|
||||
Channel: '#bot-nightly-releases'
|
||||
Message: '$(Build.DefinitionName)-$(Build.BuildNumber) finished with a $(Agent.JobStatus) status.'
|
||||
condition: and(always(), eq(variables['Build.Reason'], 'Schedule'))
|
||||
PathtoPublish: '$(Build.SourcesDirectory)/out'
|
||||
ArtifactName: out
|
||||
condition: and(succeeded(), eq(variables['ELECTRON_RELEASE'], '1'))
|
||||
|
||||
- task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3
|
||||
|
||||
Reference in New Issue
Block a user