@quenty/nevermore-cli 4.43.0-canary.783.30683872723.0 → 4.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -14
- package/README.md +10 -0
- package/dist/commands/batch-command/batch-deploy-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-deploy-command.js +101 -37
- package/dist/commands/batch-command/batch-deploy-command.js.map +1 -1
- package/dist/commands/deploy-command/index.d.ts +2 -0
- package/dist/commands/deploy-command/index.d.ts.map +1 -1
- package/dist/commands/deploy-command/index.js +35 -22
- package/dist/commands/deploy-command/index.js.map +1 -1
- package/dist/utils/job-context/base-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/base-job-context.js +1 -2
- package/dist/utils/job-context/base-job-context.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts +8 -4
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +12 -18
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.js +15 -3
- package/dist/utils/testing/parsers/batch-log-parser.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js +30 -0
- package/dist/utils/testing/parsers/batch-log-parser.test.js.map +1 -1
- package/dist/utils/watch/github-context.test.js +9 -1
- package/dist/utils/watch/github-context.test.js.map +1 -1
- package/dist/utils/watch/http-watch-registry.js +5 -3
- package/dist/utils/watch/http-watch-registry.js.map +1 -1
- package/dist/utils/watch/stream-watch-loop.d.ts.map +1 -1
- package/dist/utils/watch/stream-watch-loop.js +42 -51
- package/dist/utils/watch/stream-watch-loop.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/batch-command/batch-deploy-command.ts +135 -41
- package/src/commands/deploy-command/index.ts +41 -24
- package/src/utils/job-context/base-job-context.ts +1 -2
- package/src/utils/open-cloud/open-cloud-client.ts +12 -19
- package/src/utils/testing/parsers/batch-log-parser.test.ts +36 -0
- package/src/utils/testing/parsers/batch-log-parser.ts +16 -3
- package/src/utils/watch/github-context.test.ts +10 -1
- package/src/utils/watch/http-watch-registry.ts +5 -3
- package/src/utils/watch/stream-watch-loop.ts +73 -59
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -41,35 +41,17 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
41
41
|
else {
|
|
42
42
|
process.on('SIGINT', onSigint);
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const built = new Map(entries.map((e) => [e.watchName, e.baseline]));
|
|
52
|
-
/**
|
|
53
|
-
* The last service-side token each watch was settled at. Purely an
|
|
54
|
-
* optimization — a reconnect that reports the same token needs no Open Cloud
|
|
55
|
-
* call — and deliberately not advanced when a rebuild fails, so the next
|
|
56
|
-
* `ready` retries it.
|
|
57
|
-
*/
|
|
58
|
-
const settledAt = new Map();
|
|
44
|
+
// One record per watch rather than parallel maps under the same key: every
|
|
45
|
+
// field above is a fact about the same thing, and holding them apart only
|
|
46
|
+
// creates the possibility of disagreeing about which watches exist.
|
|
47
|
+
const watches = new Map(entries.map((e) => [
|
|
48
|
+
e.watchName,
|
|
49
|
+
{ entry: e, built: e.baseline, unreadable: false },
|
|
50
|
+
]));
|
|
59
51
|
let redeploys = 0;
|
|
60
52
|
let failures = 0;
|
|
61
53
|
let lastDropReason;
|
|
62
54
|
let unobservable = false;
|
|
63
|
-
/**
|
|
64
|
-
* Watches the service has told us it cannot read, from either direction:
|
|
65
|
-
* the `ready` snapshot on connect, or a `poll-failed` event as it happens.
|
|
66
|
-
*
|
|
67
|
-
* Both are needed. A connection opened before the service's first failed
|
|
68
|
-
* poll gets a clean snapshot, and the next one is an hour away — so without
|
|
69
|
-
* the live tail a private base place would look fine for an hour before the
|
|
70
|
-
* watch admitted it was doing nothing.
|
|
71
|
-
*/
|
|
72
|
-
const unreadable = new Set();
|
|
73
55
|
/**
|
|
74
56
|
* Give up the stream once nothing on it can ever fire, so the caller can
|
|
75
57
|
* poll instead — this machine's credentials reach places the service's
|
|
@@ -79,13 +61,23 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
79
61
|
// Counted against every entry this run owns, not against what the last
|
|
80
62
|
// frame happened to mention: a watch the service has stopped reporting
|
|
81
63
|
// altogether can no more produce a notification than one it cannot read.
|
|
82
|
-
|
|
64
|
+
const all = [...watches.values()];
|
|
65
|
+
if (all.length === 0 || !all.every((w) => w.unreadable)) {
|
|
83
66
|
return false;
|
|
84
67
|
}
|
|
85
68
|
unobservable = true;
|
|
86
69
|
controller.abort();
|
|
87
70
|
return true;
|
|
88
71
|
};
|
|
72
|
+
/** Marks a watch unreadable, and says whether that was news worth printing. */
|
|
73
|
+
const markUnreadable = (name) => {
|
|
74
|
+
const state = watches.get(name);
|
|
75
|
+
if (state == null || state.unreadable) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
state.unreadable = true;
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
89
81
|
OutputHelper.info(`Watching ${entries.length} base place${entries.length === 1 ? '' : 's'} over the watch service. Ctrl-C to stop.`);
|
|
90
82
|
for (const entry of entries) {
|
|
91
83
|
OutputHelper.info(` ${entry.label} — base place ${entry.placeId} (${entry.versionType}${entry.baseline == null ? '' : ` v${entry.baseline}`})`);
|
|
@@ -99,8 +91,9 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
99
91
|
* are not comparable — so a change is confirmed here by asking Open Cloud
|
|
100
92
|
* what the place is actually at, which is the same number the build records.
|
|
101
93
|
*/
|
|
102
|
-
const settleAsync = async (
|
|
103
|
-
|
|
94
|
+
const settleAsync = async (state, token) => {
|
|
95
|
+
const entry = state.entry;
|
|
96
|
+
if (token != null && state.settledAt === token) {
|
|
104
97
|
return;
|
|
105
98
|
}
|
|
106
99
|
let latest;
|
|
@@ -114,16 +107,16 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
114
107
|
(err instanceof Error ? err.message : String(err)));
|
|
115
108
|
return;
|
|
116
109
|
}
|
|
117
|
-
const previous = built
|
|
110
|
+
const previous = state.built;
|
|
118
111
|
const settle = () => {
|
|
119
112
|
if (token != null) {
|
|
120
|
-
settledAt
|
|
113
|
+
state.settledAt = token;
|
|
121
114
|
}
|
|
122
115
|
};
|
|
123
116
|
if (previous === undefined) {
|
|
124
117
|
// Never built here and nothing locked: adopt what is there rather than
|
|
125
118
|
// reading "unknown -> something" as a change.
|
|
126
|
-
built
|
|
119
|
+
state.built = latest;
|
|
127
120
|
settle();
|
|
128
121
|
return;
|
|
129
122
|
}
|
|
@@ -135,7 +128,7 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
135
128
|
try {
|
|
136
129
|
await redeployAsync(entry, latest);
|
|
137
130
|
redeploys++;
|
|
138
|
-
built
|
|
131
|
+
state.built = latest;
|
|
139
132
|
settle();
|
|
140
133
|
OutputHelper.info(`${entry.label}: rebuilt from v${latest}.`);
|
|
141
134
|
}
|
|
@@ -156,7 +149,7 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
156
149
|
// Every connection starts here, including reconnects. Nothing is
|
|
157
150
|
// replayed while a client is away, so this is the whole catch-up.
|
|
158
151
|
onReadyAsync: async (ready) => {
|
|
159
|
-
const ours = ready.watches.filter((s) =>
|
|
152
|
+
const ours = ready.watches.filter((s) => watches.has(s.name));
|
|
160
153
|
// A source the service cannot read never fires, and the socket says
|
|
161
154
|
// nothing about it — the failure is visible only here. Waiting
|
|
162
155
|
// silently forever is the worst of the options, so a monitor that can
|
|
@@ -178,41 +171,40 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
178
171
|
// the same set means a partial snapshot falls back too, instead of
|
|
179
172
|
// holding the socket for the ones that remain and silently never
|
|
180
173
|
// rebuilding the rest.
|
|
181
|
-
const missing = [...
|
|
174
|
+
const missing = [...watches.keys()].filter((name) => !ready.watches.some((s) => s.name === name));
|
|
182
175
|
for (const name of missing) {
|
|
183
|
-
if (
|
|
176
|
+
if (!markUnreadable(name)) {
|
|
184
177
|
continue;
|
|
185
178
|
}
|
|
186
|
-
|
|
187
|
-
OutputHelper.warn(`The watch monitor no longer holds ${byWatchName.get(name).label} — another checkout has probably re-registered it.`);
|
|
179
|
+
OutputHelper.warn(`The watch monitor no longer holds ${watches.get(name).entry.label} — another checkout has probably re-registered it.`);
|
|
188
180
|
}
|
|
189
181
|
if (abandonIfNothingLeftToWatch()) {
|
|
190
182
|
return;
|
|
191
183
|
}
|
|
192
184
|
for (const status of failedToRead) {
|
|
193
|
-
if (
|
|
185
|
+
if (!markUnreadable(status.name)) {
|
|
194
186
|
continue;
|
|
195
187
|
}
|
|
196
|
-
|
|
197
|
-
OutputHelper.warn(`The watch service cannot read ${byWatchName.get(status.name).label}` + (status.sourceError ? `: ${status.sourceError}` : '.'));
|
|
188
|
+
OutputHelper.warn(`The watch service cannot read ${watches.get(status.name).entry.label}` + (status.sourceError ? `: ${status.sourceError}` : '.'));
|
|
198
189
|
}
|
|
199
190
|
if (abandonIfNothingLeftToWatch()) {
|
|
200
191
|
return;
|
|
201
192
|
}
|
|
202
193
|
for (const status of ours) {
|
|
203
|
-
|
|
204
|
-
|
|
194
|
+
const state = watches.get(status.name);
|
|
195
|
+
if (!state.unreadable) {
|
|
196
|
+
await settleAsync(state, status.currentVersion);
|
|
205
197
|
}
|
|
206
198
|
}
|
|
207
199
|
},
|
|
208
200
|
onNotifyAsync: async (notify) => {
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
201
|
+
const state = watches.get(notify.watchName);
|
|
202
|
+
if (!state) {
|
|
211
203
|
OutputHelper.verbose(`Ignoring a notification for "${notify.watchName}", which this ` +
|
|
212
204
|
'run does not watch.');
|
|
213
205
|
return;
|
|
214
206
|
}
|
|
215
|
-
await settleAsync(
|
|
207
|
+
await settleAsync(state, notify.version);
|
|
216
208
|
},
|
|
217
209
|
onEvent: (message) => {
|
|
218
210
|
OutputHelper.verbose(` [${message.event.type}] ${message.event.message}`);
|
|
@@ -222,14 +214,13 @@ export async function runStreamWatchLoopAsync(options) {
|
|
|
222
214
|
// is polling something that could have been streamed, which still
|
|
223
215
|
// works.
|
|
224
216
|
const name = message.event.watchName;
|
|
225
|
-
if (message.event.type !== 'poll-failed' ||
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
if (message.event.type !== 'poll-failed' || name == null) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!markUnreadable(name)) {
|
|
229
221
|
return;
|
|
230
222
|
}
|
|
231
|
-
|
|
232
|
-
OutputHelper.warn(`The watch service cannot read ${byWatchName.get(name).label}: ${message.event.message}`);
|
|
223
|
+
OutputHelper.warn(`The watch service cannot read ${watches.get(name).entry.label}: ${message.event.message}`);
|
|
233
224
|
abandonIfNothingLeftToWatch();
|
|
234
225
|
},
|
|
235
226
|
// The stream swallows a handler's throw so it cannot strand the watch
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-watch-loop.js","sourceRoot":"","sources":["../../../src/utils/watch/stream-watch-loop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAU1D;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,MAAyB;IAC9C,OAAO,CACL,MAAM,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,yBAAyB,IAAI,CAAC,CAAC,GAAG,CAAC,CAC1E,CAAC;AACJ,CAAC;AA2CD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAA+B;IAE/B,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAE3D,6EAA6E;IAC7E,2EAA2E;IAC3E,qEAAqE;IACrE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC3B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjC,CAAC;
|
|
1
|
+
{"version":3,"file":"stream-watch-loop.js","sourceRoot":"","sources":["../../../src/utils/watch/stream-watch-loop.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/nevermore-cli",
|
|
3
|
-
"version": "4.43.
|
|
3
|
+
"version": "4.43.1",
|
|
4
4
|
"description": "CLI interface for Nevermore",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@quenty/cli-output-helpers": "1.17.1",
|
|
29
|
-
"@quenty/nevermore-cli-helpers": "1.17.0
|
|
30
|
-
"@quenty/nevermore-deploy": "1.1.0
|
|
29
|
+
"@quenty/nevermore-cli-helpers": "1.17.0",
|
|
30
|
+
"@quenty/nevermore-deploy": "1.1.0",
|
|
31
31
|
"@quenty/nevermore-template-helpers": "1.18.1",
|
|
32
|
-
"@quenty/studio-bridge": "0.18.0
|
|
32
|
+
"@quenty/studio-bridge": "0.18.0",
|
|
33
33
|
"execa": "^9.6.1",
|
|
34
34
|
"find-git-root": "^1.0.4",
|
|
35
35
|
"inquirer": "^13.2.0",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"engines": {
|
|
61
61
|
"node": ">=16"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "6964e315a963a65deddba8e347fc35288f279b3e"
|
|
64
64
|
}
|
|
@@ -43,8 +43,16 @@ import {
|
|
|
43
43
|
readPackageVersionAsync,
|
|
44
44
|
} from '../../utils/nevermore-cli-utils.js';
|
|
45
45
|
import { parseTestLogs } from '../../utils/testing/test-log-parser.js';
|
|
46
|
-
import {
|
|
47
|
-
|
|
46
|
+
import {
|
|
47
|
+
buildWatchMonitorName,
|
|
48
|
+
parseWatchOption,
|
|
49
|
+
type BasePlaceResolver,
|
|
50
|
+
type WatchOption,
|
|
51
|
+
} from '@quenty/nevermore-deploy';
|
|
52
|
+
import {
|
|
53
|
+
registerWatchesAsync,
|
|
54
|
+
type WatchCandidate,
|
|
55
|
+
} from '../../utils/watch/register-watches.js';
|
|
48
56
|
|
|
49
57
|
const SMOKE_TEST_SCRIPT_PATH = resolvePackagePath(
|
|
50
58
|
import.meta.url,
|
|
@@ -185,6 +193,27 @@ async function _runAsync(args: BatchDeployArgs): Promise<void> {
|
|
|
185
193
|
'Use --all to deploy every package with this target, or --base <ref> to change the comparison ref.'
|
|
186
194
|
);
|
|
187
195
|
}
|
|
196
|
+
// Renew anyway. Registration derives from committed config plus the lock,
|
|
197
|
+
// so it does not need this run to have deployed anything — and the lease is
|
|
198
|
+
// the only thing keeping the watch alive. Returning here instead is how a
|
|
199
|
+
// repo whose scheduled builds all diff clean quietly stops being watched,
|
|
200
|
+
// with no failure anywhere to notice.
|
|
201
|
+
if (watchOption) {
|
|
202
|
+
OutputHelper.info(
|
|
203
|
+
'Renewing watches anyway, so the lease does not lapse.'
|
|
204
|
+
);
|
|
205
|
+
await _registerWatchesAsync({
|
|
206
|
+
targetName,
|
|
207
|
+
option: watchOption,
|
|
208
|
+
// Nothing deployed, so the lock is the whole truth about what each
|
|
209
|
+
// place was built from.
|
|
210
|
+
resolver: createLockOnlyBasePlaceResolver(),
|
|
211
|
+
useGhAuth: args.watchUseGhAuth,
|
|
212
|
+
shareApiKey: args.watchShareApiKey,
|
|
213
|
+
resolveApiKeyAsync: () => getApiKeyAsync(args),
|
|
214
|
+
dryrun: args.dryrun,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
188
217
|
return;
|
|
189
218
|
}
|
|
190
219
|
|
|
@@ -197,20 +226,7 @@ async function _runAsync(args: BatchDeployArgs): Promise<void> {
|
|
|
197
226
|
// lock file, so it runs for real even on a dryrun — that is what makes the
|
|
198
227
|
// watch path testable without shipping a build.
|
|
199
228
|
if (watchOption) {
|
|
200
|
-
|
|
201
|
-
'[DRYRUN] Registering separate dryrun watches and firing them, to ' +
|
|
202
|
-
'prove routing works. This does not build or upload here — but ' +
|
|
203
|
-
'firing really does dispatch the workflow once per package, and ' +
|
|
204
|
-
'each of those runs a real deploy on the runner.'
|
|
205
|
-
);
|
|
206
|
-
if (args.watchShareApiKey) {
|
|
207
|
-
OutputHelper.warn(
|
|
208
|
-
'[DRYRUN] Registering without the Open Cloud key, so a "saved" base ' +
|
|
209
|
-
'place is skipped here even though a real run would watch it.'
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
const allPackages = await discoverAllTargetPackagesAsync(targetName);
|
|
213
|
-
// Under its own monitor name, never the one a real run owns.
|
|
229
|
+
// Under its own monitor names, never the ones a real run owns.
|
|
214
230
|
//
|
|
215
231
|
// Re-registering replaces a monitor's entire watch list, and a dryrun
|
|
216
232
|
// cannot reproduce a real registration: the Open Cloud key is resolved
|
|
@@ -219,18 +235,13 @@ async function _runAsync(args: BatchDeployArgs): Promise<void> {
|
|
|
219
235
|
// driver and its content-hash vocabulary, dropping the baselines, and
|
|
220
236
|
// deleting any "saved" watch outright, because an anonymous
|
|
221
237
|
// registration skips those.
|
|
222
|
-
await
|
|
238
|
+
await _registerWatchesAsync({
|
|
239
|
+
targetName,
|
|
223
240
|
option: watchOption,
|
|
224
|
-
monitorName: `${targetName}/dryrun`,
|
|
225
241
|
resolver: createLockOnlyBasePlaceResolver(),
|
|
226
242
|
useGhAuth: args.watchUseGhAuth,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
targetName,
|
|
230
|
-
packageName: buildTarget.packageName,
|
|
231
|
-
packagePath: buildTarget.path,
|
|
232
|
-
place: buildTarget.target,
|
|
233
|
-
})),
|
|
243
|
+
shareApiKey: args.watchShareApiKey,
|
|
244
|
+
dryrun: true,
|
|
234
245
|
});
|
|
235
246
|
}
|
|
236
247
|
return;
|
|
@@ -394,25 +405,13 @@ async function _runAsync(args: BatchDeployArgs): Promise<void> {
|
|
|
394
405
|
// re-registers the same wrong baseline. The failure becomes permanent and
|
|
395
406
|
// invisible.
|
|
396
407
|
if (watchOption && results.summary.failed === 0) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
// re-apply replaces its whole list, so registering only what changed
|
|
400
|
-
// would delete the watches of every package that happened not to change
|
|
401
|
-
// this run. Packages that did not deploy still have a lock entry, which
|
|
402
|
-
// is the correct baseline for them.
|
|
403
|
-
const allPackages = await discoverAllTargetPackagesAsync(targetName);
|
|
404
|
-
await registerWatchesAsync({
|
|
408
|
+
await _registerWatchesAsync({
|
|
409
|
+
targetName,
|
|
405
410
|
option: watchOption,
|
|
406
|
-
monitorName: targetName,
|
|
407
411
|
resolver: basePlaceResolver,
|
|
408
412
|
useGhAuth: args.watchUseGhAuth,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
targetName,
|
|
412
|
-
packageName: buildTarget.packageName,
|
|
413
|
-
packagePath: buildTarget.path,
|
|
414
|
-
place: buildTarget.target,
|
|
415
|
-
})),
|
|
413
|
+
shareApiKey: args.watchShareApiKey,
|
|
414
|
+
resolveApiKeyAsync: async () => apiKey,
|
|
416
415
|
});
|
|
417
416
|
}
|
|
418
417
|
} catch (err) {
|
|
@@ -426,6 +425,101 @@ async function _runAsync(args: BatchDeployArgs): Promise<void> {
|
|
|
426
425
|
process.exit(exitCode);
|
|
427
426
|
}
|
|
428
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Register one monitor per package, covering every package with this target.
|
|
430
|
+
*
|
|
431
|
+
* Per package rather than one for the batch, because `deploy run --watch`
|
|
432
|
+
* registers a package's places too. Two names for one package's watches means
|
|
433
|
+
* two monitors on the same base place, so one artist publish dispatches twice
|
|
434
|
+
* and each rebuild races the other's lock write. Both paths take the name from
|
|
435
|
+
* `buildWatchMonitorName`, so registering from either replaces the same list.
|
|
436
|
+
*
|
|
437
|
+
* Every package with the target, never just the ones that deployed: a re-apply
|
|
438
|
+
* replaces a monitor's whole list, and a package that didn't change still has a
|
|
439
|
+
* lock entry, which is exactly the right baseline for it.
|
|
440
|
+
*/
|
|
441
|
+
async function _registerWatchesAsync(options: {
|
|
442
|
+
targetName: string;
|
|
443
|
+
option: WatchOption;
|
|
444
|
+
resolver: BasePlaceResolver;
|
|
445
|
+
useGhAuth?: boolean;
|
|
446
|
+
/** Whether `--watch-share-api-key` was passed. A dryrun never shares one. */
|
|
447
|
+
shareApiKey?: boolean;
|
|
448
|
+
/** Called only if a key is actually going to be sent. */
|
|
449
|
+
resolveApiKeyAsync?: () => Promise<string>;
|
|
450
|
+
dryrun?: boolean;
|
|
451
|
+
}): Promise<void> {
|
|
452
|
+
const { targetName } = options;
|
|
453
|
+
|
|
454
|
+
if (options.dryrun) {
|
|
455
|
+
OutputHelper.info(
|
|
456
|
+
'[DRYRUN] Registering separate dryrun watches and firing them, to ' +
|
|
457
|
+
'prove routing works. This does not build or upload here — but ' +
|
|
458
|
+
'firing really does dispatch the workflow once per package, and ' +
|
|
459
|
+
'each of those runs a real deploy on the runner.'
|
|
460
|
+
);
|
|
461
|
+
if (options.shareApiKey) {
|
|
462
|
+
OutputHelper.warn(
|
|
463
|
+
'[DRYRUN] Registering without the Open Cloud key, so a "saved" base ' +
|
|
464
|
+
'place is skipped here even though a real run would watch it.'
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const robloxApiKey =
|
|
470
|
+
options.shareApiKey && !options.dryrun
|
|
471
|
+
? await options.resolveApiKeyAsync?.()
|
|
472
|
+
: undefined;
|
|
473
|
+
|
|
474
|
+
const allPackages = await discoverAllTargetPackagesAsync(targetName);
|
|
475
|
+
|
|
476
|
+
// Places that never asked to be watched are dropped here rather than passed
|
|
477
|
+
// in to be reported as skips. Most places in a batch are in that state, and
|
|
478
|
+
// per-package registration would otherwise warn about each package that has
|
|
479
|
+
// no watch at all. A place that asked and can't be watched still goes
|
|
480
|
+
// through, because that is the case worth saying out loud.
|
|
481
|
+
const byPackage = new Map<string, WatchCandidate[]>();
|
|
482
|
+
for (const buildTarget of flattenToBatchTargets(allPackages)) {
|
|
483
|
+
if (buildTarget.target.watch == null) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const candidates = byPackage.get(buildTarget.packageName) ?? [];
|
|
487
|
+
candidates.push({
|
|
488
|
+
targetName,
|
|
489
|
+
packageName: buildTarget.packageName,
|
|
490
|
+
packagePath: buildTarget.path,
|
|
491
|
+
place: buildTarget.target,
|
|
492
|
+
});
|
|
493
|
+
byPackage.set(buildTarget.packageName, candidates);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (byPackage.size === 0) {
|
|
497
|
+
OutputHelper.warn(
|
|
498
|
+
`--watch was requested, but no place in a "${targetName}" target has a ` +
|
|
499
|
+
'"watch" field. Add one naming the workflow that rebuilds it.'
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
for (const [packageName, candidates] of byPackage) {
|
|
505
|
+
await registerWatchesAsync({
|
|
506
|
+
option: options.option,
|
|
507
|
+
monitorName: buildWatchMonitorName({
|
|
508
|
+
packageName,
|
|
509
|
+
targetName,
|
|
510
|
+
dryrun: options.dryrun,
|
|
511
|
+
}),
|
|
512
|
+
resolver: options.resolver,
|
|
513
|
+
useGhAuth: options.useGhAuth,
|
|
514
|
+
robloxApiKey,
|
|
515
|
+
// Firing proves the workflow really receives the selector, which is the
|
|
516
|
+
// half that historically breaks. Only a dryrun asks for it.
|
|
517
|
+
triggerAfterRegister: options.dryrun,
|
|
518
|
+
candidates,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
429
523
|
function _annotateSmokeTestFailure(logs: string): string {
|
|
430
524
|
const header =
|
|
431
525
|
'Post-deploy smoke test failed. The deploy itself succeeded, but a server ' +
|
|
@@ -39,16 +39,20 @@ import {
|
|
|
39
39
|
readPackageVersionAsync,
|
|
40
40
|
} from '../../utils/nevermore-cli-utils.js';
|
|
41
41
|
import {
|
|
42
|
+
WATCH_MODES,
|
|
43
|
+
buildWatchMonitorName,
|
|
44
|
+
checkBasePlaceWatchable,
|
|
42
45
|
formatTargetSelector,
|
|
43
|
-
isBasePlaceVersionKeyword,
|
|
44
46
|
loadDeployConfigAsync,
|
|
45
47
|
parseTargetSelector,
|
|
46
48
|
parseWatchOption,
|
|
47
49
|
resolveDeployConfigPath,
|
|
48
50
|
resolveDeployTargetPlaces,
|
|
51
|
+
resolveWatchDelivery,
|
|
49
52
|
toManifestPlaceInfo,
|
|
50
53
|
type BasePlaceResolver,
|
|
51
54
|
type DeployConfig,
|
|
55
|
+
type WatchMode,
|
|
52
56
|
} from '@quenty/nevermore-deploy';
|
|
53
57
|
import {
|
|
54
58
|
registerWatchesAsync,
|
|
@@ -77,14 +81,10 @@ const MULTI_PLACE_CONCURRENCY = 10;
|
|
|
77
81
|
/**
|
|
78
82
|
* Which base places a local watch polls.
|
|
79
83
|
*
|
|
80
|
-
* Deliberately looser than the cloud plan
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* has Open Cloud credentials and can.
|
|
85
|
-
*
|
|
86
|
-
* An exact version pin is still excluded, for the same reason as always: it
|
|
87
|
-
* means "hold this still", so there is nothing to follow.
|
|
84
|
+
* Deliberately looser than the cloud plan, in exactly one way: `watch` is a
|
|
85
|
+
* dispatch address, nothing is dispatched locally, so requiring it would only
|
|
86
|
+
* stop a developer hot-reloading a place that never intends to use CI.
|
|
87
|
+
* Everything about the base place itself is the shared rule.
|
|
88
88
|
*/
|
|
89
89
|
async function _buildLocalWatchEntriesAsync(
|
|
90
90
|
config: DeployConfig,
|
|
@@ -95,21 +95,19 @@ async function _buildLocalWatchEntriesAsync(
|
|
|
95
95
|
const entries: LocalWatchEntry[] = [];
|
|
96
96
|
for (const place of resolveDeployTargetPlaces(config, targetName)) {
|
|
97
97
|
const basePlace = place.basePlace;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
) {
|
|
98
|
+
// The same rule a registration uses, asked with `credentialed: true`: this
|
|
99
|
+
// machine holds the Open Cloud key, so "saved" is readable here even when
|
|
100
|
+
// the service could not see it. Only the workflow half differs, and a local
|
|
101
|
+
// watch has nothing to dispatch.
|
|
102
|
+
const check = checkBasePlaceWatchable(basePlace, { credentialed: true });
|
|
103
|
+
if (basePlace == null || !check.watchable) {
|
|
105
104
|
continue;
|
|
106
105
|
}
|
|
107
|
-
const versionType = basePlace.version ?? 'published';
|
|
108
106
|
entries.push({
|
|
109
107
|
label: formatTargetSelector({ targetName, placeName: place.name }),
|
|
110
108
|
universeId: basePlace.universeId,
|
|
111
109
|
placeId: basePlace.placeId,
|
|
112
|
-
versionType,
|
|
110
|
+
versionType: check.versionType,
|
|
113
111
|
baseline: await resolver.peekAsync(packagePath, basePlace),
|
|
114
112
|
});
|
|
115
113
|
}
|
|
@@ -130,6 +128,7 @@ export interface DeployArgs extends NevermoreGlobalArgs {
|
|
|
130
128
|
output?: string;
|
|
131
129
|
logs?: boolean;
|
|
132
130
|
watch?: string;
|
|
131
|
+
watchMode?: WatchMode;
|
|
133
132
|
watchShareApiKey?: boolean;
|
|
134
133
|
watchUseGhAuth?: boolean;
|
|
135
134
|
}
|
|
@@ -303,6 +302,16 @@ export class DeployCommand<T> implements CommandModule<T, DeployArgs> {
|
|
|
303
302
|
'Takes the register endpoint URL, ending in the lease: https://<watch-service>/v1/register/7d',
|
|
304
303
|
type: 'string',
|
|
305
304
|
})
|
|
305
|
+
.option('watch-mode', {
|
|
306
|
+
describe:
|
|
307
|
+
'How the watch reaches whoever rebuilds: "dispatch" fires the workflow, ' +
|
|
308
|
+
'"notify" holds a stream here and rebuilds in this terminal. ' +
|
|
309
|
+
'Defaults to "auto", which dispatches on GitHub Actions and notifies elsewhere — ' +
|
|
310
|
+
'set it explicitly in any other automated context, where notifying would hang the run.',
|
|
311
|
+
type: 'string',
|
|
312
|
+
choices: WATCH_MODES,
|
|
313
|
+
default: 'auto',
|
|
314
|
+
})
|
|
306
315
|
.option('watch-use-gh-auth', {
|
|
307
316
|
describe:
|
|
308
317
|
'Let the GitHub CLI supply the watch token when no environment variable does. ' +
|
|
@@ -420,7 +429,11 @@ export class DeployCommand<T> implements CommandModule<T, DeployArgs> {
|
|
|
420
429
|
// registration skips those.
|
|
421
430
|
await registerWatchesAsync({
|
|
422
431
|
option: watchOption,
|
|
423
|
-
monitorName:
|
|
432
|
+
monitorName: buildWatchMonitorName({
|
|
433
|
+
packageName,
|
|
434
|
+
targetName: watchedTarget,
|
|
435
|
+
dryrun: true,
|
|
436
|
+
}),
|
|
424
437
|
resolver: createLockOnlyBasePlaceResolver(),
|
|
425
438
|
useGhAuth: args.watchUseGhAuth,
|
|
426
439
|
triggerAfterRegister: true,
|
|
@@ -636,7 +649,7 @@ export class DeployCommand<T> implements CommandModule<T, DeployArgs> {
|
|
|
636
649
|
// Only after a clean deploy: what happens next records the base place
|
|
637
650
|
// version that just shipped, which is not a fact yet if the deploy failed.
|
|
638
651
|
if (watchOption && first.failed === 0) {
|
|
639
|
-
if (isCI()) {
|
|
652
|
+
if (resolveWatchDelivery(args.watchMode, isCI()) === 'dispatch') {
|
|
640
653
|
// The monitor is named for the whole target, so it has to carry every
|
|
641
654
|
// place in that target — not just the ones this invocation deployed.
|
|
642
655
|
// Re-registering replaces its watch list, so a narrowed selector
|
|
@@ -644,9 +657,10 @@ export class DeployCommand<T> implements CommandModule<T, DeployArgs> {
|
|
|
644
657
|
// places' watches. Places that did not deploy keep their lock baseline.
|
|
645
658
|
watchResult = await registerWatchesAsync({
|
|
646
659
|
option: watchOption,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
660
|
+
monitorName: buildWatchMonitorName({
|
|
661
|
+
packageName,
|
|
662
|
+
targetName: watchedTarget,
|
|
663
|
+
}),
|
|
650
664
|
resolver: first.resolver,
|
|
651
665
|
useGhAuth: args.watchUseGhAuth,
|
|
652
666
|
robloxApiKey: args.watchShareApiKey ? apiKey : undefined,
|
|
@@ -680,7 +694,10 @@ export class DeployCommand<T> implements CommandModule<T, DeployArgs> {
|
|
|
680
694
|
|
|
681
695
|
const notify = await tryRegisterNotifyWatchesAsync({
|
|
682
696
|
option: watchOption,
|
|
683
|
-
monitorName:
|
|
697
|
+
monitorName: buildWatchMonitorName({
|
|
698
|
+
packageName,
|
|
699
|
+
targetName: watchedTarget,
|
|
700
|
+
}),
|
|
684
701
|
entries: localEntries,
|
|
685
702
|
useGhAuth: args.watchUseGhAuth,
|
|
686
703
|
robloxApiKey: args.watchShareApiKey ? apiKey : undefined,
|
|
@@ -150,8 +150,7 @@ export abstract class BaseJobContext implements JobContext {
|
|
|
150
150
|
// Waiter: the in-flight build was started under a different
|
|
151
151
|
// packageName, so emit our own 'building' phase change for the reporter.
|
|
152
152
|
const resolvedName =
|
|
153
|
-
options.packageName ??
|
|
154
|
-
path.basename(options.packagePath);
|
|
153
|
+
options.packageName ?? path.basename(options.packagePath);
|
|
155
154
|
this._reporter.onPackagePhaseChange(resolvedName, 'building');
|
|
156
155
|
return existing.promise;
|
|
157
156
|
}
|