12
12
import buildExecuteLifecycleScript from "./commands/_execute-lifecycle-script.js" ;
13
13
import { ConsoleReporter , JSONReporter } from "kreporters" ;
14
14
import { MessageError } from "../errors.js" ;
15
- import * as network from "../util/network.js" ;
16
15
import * as commands from "./commands/index.js" ;
16
+ import * as constants from "../constants.js" ;
17
+ import * as network from "../util/network.js" ;
18
+
17
19
import aliases from "./aliases.js" ;
18
20
import Config from "../config.js" ;
21
+ import onDeath from "death" ;
22
+
23
+ const net = require ( "net" ) ;
24
+ const path = require ( "path" ) ;
25
+ const fs = require ( "fs" ) ;
19
26
20
- let loudRejection = require ( "loud-rejection" ) ;
21
- let commander = require ( "commander" ) ;
22
- let invariant = require ( "invariant" ) ;
23
- let pkg = require ( "../../package" ) ;
24
- let _ = require ( "lodash" ) ;
27
+ let loudRejection = require ( "loud-rejection" ) ;
28
+ let commander = require ( "commander" ) ;
29
+ let invariant = require ( "invariant" ) ;
30
+ let pkg = require ( "../../package" ) ;
31
+ let _ = require ( "lodash" ) ;
32
+ let lastWillExpressed = false ;
25
33
26
34
loudRejection ( ) ;
27
35
@@ -35,6 +43,10 @@ commander.option("--modules-folder [path]", "rather than installing modules into
35
43
"folder relative to the cwd, output them here" ) ;
36
44
commander . option ( "--packages-root [path]" , "rather than storing modules into a global packages root," +
37
45
"store them here" ) ;
46
+ commander . option (
47
+ "--force-single-instance" ,
48
+ "pause and wait if other instances are running on the same folder"
49
+ ) ;
38
50
// get command name
39
51
let commandName = args . splice ( 2 , 1 ) [ 0 ] || "" ;
40
52
@@ -99,12 +111,70 @@ if (network.isOffline()) {
99
111
}
100
112
101
113
//
102
- config . init ( ) . then ( function ( ) {
114
+ const run = ( ) => {
103
115
return command . run ( config , reporter , commander , commander . args ) . then ( function ( ) {
104
116
reporter . close ( ) ;
105
117
reporter . footer ( true ) ;
106
- process . exit ( ) ;
107
118
} ) ;
119
+ } ;
120
+
121
+
122
+ //
123
+ const runEventually = ( ) => {
124
+ return new Promise ( ( ok ) => {
125
+ const socketFile = path . join ( config . cwd , constants . SINGLE_SOCKET_FILENAME ) ;
126
+ const clients = [ ] ;
127
+ const unixServer = net . createServer ( ( client ) => {
128
+ clients . push ( client ) ;
129
+ } ) ;
130
+ unixServer . on ( "error" , ( ) => {
131
+ // another process exists, wait until it dies.
132
+ reporter . warn ( "waiting until the other kpm instance finish." ) ;
133
+
134
+ let socket = net . createConnection ( socketFile ) ;
135
+
136
+ socket . on ( "connect" , ( ) => { } ) . on ( "data" , ( ) => {
137
+ socket . unref ( ) ;
138
+ ok ( runEventually ( ) . then ( process . exit ) ) ;
139
+ } ) . on ( "error" , ( e ) => {
140
+ // the process finished while we were handling the error.
141
+ if ( e . code === "ECONNREFUSED" ) {
142
+ try {
143
+ fs . unlinkSync ( socketFile ) ;
144
+ } catch ( e ) { } // some other instance won the race to delete the file
145
+ }
146
+
147
+ ok ( runEventually ( ) . then ( process . exit ) ) ;
148
+ } ) ;
149
+
150
+ } ) ;
151
+
152
+ const clean = ( ) => {
153
+ // clean after ourself.
154
+ clients . forEach ( ( client ) => {
155
+ client . write ( "closing. kthanx, bye." ) ;
156
+ } ) ;
157
+ unixServer . close ( ) ;
158
+ process . exit ( ) ;
159
+ } ;
160
+
161
+ if ( ! lastWillExpressed ) {
162
+ onDeath ( clean ) ;
163
+ lastWillExpressed = true ;
164
+ }
165
+
166
+ unixServer . listen ( socketFile , ( ) => {
167
+ ok ( run ( ) . then ( clean ) ) ;
168
+ } ) ;
169
+ } ) ;
170
+ } ;
171
+
172
+ //
173
+ config . init ( ) . then ( ( ) => {
174
+ if ( commander . forceSingleInstance ) {
175
+ return runEventually ( ) ;
176
+ }
177
+ return run ( ) . then ( process . exit ) ;
108
178
} ) . catch ( function ( errs ) {
109
179
function logError ( err ) {
110
180
if ( err instanceof MessageError ) {
0 commit comments