@superpanel-sdk/runtime 0.2.3 → 0.5.0
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/README.md +28 -8
- package/dist/builder/codegen/emit.d.ts +29 -11
- package/dist/builder/codegen/emit.js +328 -61
- package/dist/builder/codegen/generate.d.ts +21 -0
- package/dist/builder/codegen/generate.js +152 -20
- package/dist/builder/company-import-graph.d.ts +2 -0
- package/dist/builder/company-import-graph.js +40 -8
- package/dist/builder/company-imports.d.ts +24 -0
- package/dist/builder/company-imports.js +114 -0
- package/dist/builder/compile/compile-sequence.d.ts +59 -8
- package/dist/builder/compile/compile-sequence.js +279 -77
- package/dist/builder/compile/function-code.d.ts +12 -2
- package/dist/builder/compile/function-code.js +87 -39
- package/dist/builder/compile/registry.d.ts +7 -3
- package/dist/builder/compile/registry.js +24 -23
- package/dist/builder/load.d.ts +70 -10
- package/dist/builder/load.js +250 -61
- package/dist/builder/paths.d.ts +3 -0
- package/dist/builder/paths.js +16 -0
- package/dist/builder/schedules.d.ts +23 -0
- package/dist/builder/schedules.js +102 -0
- package/dist/builder/slot-schema-validation.d.ts +4 -0
- package/dist/builder/slot-schema-validation.js +10 -0
- package/dist/builder/typecheck.d.ts +8 -0
- package/dist/builder/typecheck.js +105 -0
- package/dist/builder/types.d.ts +91 -19
- package/dist/builder/types.js +2 -1
- package/dist/cli/auth/auth.d.ts +37 -0
- package/dist/cli/auth/auth.js +211 -0
- package/dist/cli/auth/backend-url.d.ts +5 -0
- package/dist/cli/auth/backend-url.js +25 -0
- package/dist/cli/auth/clipboard.d.ts +9 -0
- package/dist/cli/auth/clipboard.js +38 -0
- package/dist/cli/auth/http.d.ts +20 -0
- package/dist/cli/auth/http.js +27 -0
- package/dist/cli/auth/index.d.ts +10 -0
- package/dist/cli/auth/index.js +27 -0
- package/dist/cli/auth/progress.d.ts +12 -0
- package/dist/cli/auth/progress.js +15 -0
- package/dist/cli/auth/serve-login.d.ts +8 -0
- package/dist/cli/auth/serve-login.js +24 -0
- package/dist/cli/compile/index.js +3 -0
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.js +52 -0
- package/dist/cli/index.js +28 -8
- package/dist/cli/serve/index.d.ts +73 -3
- package/dist/cli/serve/index.js +365 -116
- package/dist/cli/type-gen/index.js +9 -8
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +7 -0
- package/dist/compute-host/compute-host.d.ts +15 -23
- package/dist/compute-host/compute-host.js +25 -28
- package/dist/compute-host/index.d.ts +2 -4
- package/dist/compute-host/index.js +1 -2
- package/dist/compute-host/source-compute-host.d.ts +11 -9
- package/dist/compute-host/source-compute-host.js +46 -32
- package/dist/core/case-orchestrator/contact-hours.d.ts +50 -0
- package/dist/core/case-orchestrator/contact-hours.js +108 -0
- package/dist/core/case-orchestrator/follow-ups.d.ts +31 -0
- package/dist/core/case-orchestrator/follow-ups.js +103 -0
- package/dist/core/case-orchestrator/index.d.ts +6 -0
- package/dist/core/case-orchestrator/index.js +6 -0
- package/dist/core/case-orchestrator/render.d.ts +75 -0
- package/dist/core/case-orchestrator/render.js +239 -0
- package/dist/core/case-orchestrator/time.d.ts +11 -0
- package/dist/core/case-orchestrator/time.js +29 -0
- package/dist/core/case-orchestrator/tools.d.ts +29 -0
- package/dist/core/case-orchestrator/tools.js +31 -0
- package/dist/core/case-orchestrator/types.d.ts +753 -0
- package/dist/core/case-orchestrator/types.js +46 -0
- package/dist/core/case-orchestrator/validate.d.ts +11 -0
- package/dist/core/case-orchestrator/validate.js +63 -0
- package/dist/core/context/call.d.ts +291 -0
- package/dist/core/context/call.js +94 -0
- package/dist/core/context/channels.d.ts +60 -0
- package/dist/core/context/channels.js +35 -0
- package/dist/core/context/contact.d.ts +96 -6
- package/dist/core/context/contact.js +48 -2
- package/dist/core/context/email-message.d.ts +159 -0
- package/dist/core/context/email-message.js +59 -0
- package/dist/core/context/email.d.ts +97 -0
- package/dist/core/context/email.js +91 -0
- package/dist/core/context/exchanges.d.ts +142 -0
- package/dist/core/context/exchanges.js +63 -0
- package/dist/core/context/instances.d.ts +278 -0
- package/dist/core/context/instances.js +123 -0
- package/dist/core/context/kv.d.ts +113 -0
- package/dist/core/context/kv.js +60 -0
- package/dist/core/context/platform-registry.d.ts +509 -4
- package/dist/core/context/platform-registry.js +14 -2
- package/dist/core/context/platform.d.ts +2 -1
- package/dist/core/context/sms.d.ts +147 -0
- package/dist/core/context/sms.js +57 -0
- package/dist/core/context/types.d.ts +51 -48
- package/dist/core/context/types.js +1 -7
- package/dist/core/conversation/index.d.ts +31 -0
- package/dist/core/conversation/index.js +14 -0
- package/dist/core/functions/define/bind.d.ts +25 -0
- package/dist/core/functions/define/bind.js +14 -0
- package/dist/core/functions/define/case.d.ts +9 -0
- package/dist/core/functions/define/case.js +13 -0
- package/dist/core/functions/define/events.d.ts +20 -0
- package/dist/core/functions/define/events.js +21 -0
- package/dist/core/functions/define/no-case.d.ts +4 -0
- package/dist/core/functions/define/no-case.js +9 -0
- package/dist/core/functions/events.d.ts +247 -0
- package/dist/core/functions/events.js +32 -0
- package/dist/core/functions/func-ref.d.ts +11 -2
- package/dist/core/functions/schedules.d.ts +51 -0
- package/dist/core/functions/schedules.js +20 -0
- package/dist/core/functions/types.d.ts +62 -51
- package/dist/core/functions/types.js +6 -1
- package/dist/core/office-hours/index.d.ts +39 -0
- package/dist/core/office-hours/index.js +187 -0
- package/dist/core/page/define.d.ts +834 -0
- package/dist/core/page/define.js +477 -0
- package/dist/core/page/ops.d.ts +58 -0
- package/dist/core/page/ops.js +220 -0
- package/dist/core/page/render.d.ts +253 -0
- package/dist/core/page/render.js +840 -0
- package/dist/core/page/time-format.d.ts +19 -0
- package/dist/core/page/time-format.js +41 -0
- package/dist/core/page/timeline.d.ts +40 -0
- package/dist/core/page/timeline.js +2 -0
- package/dist/core/page/typed.d.ts +642 -0
- package/dist/core/page/typed.js +2 -0
- package/dist/core/page/workspace.d.ts +33 -0
- package/dist/core/page/workspace.js +231 -0
- package/dist/core/party/index.d.ts +3 -0
- package/dist/core/party/index.js +3 -0
- package/dist/core/party/schema.d.ts +137 -0
- package/dist/core/party/schema.js +80 -0
- package/dist/core/party/types.d.ts +40 -0
- package/dist/core/party/types.js +15 -0
- package/dist/core/sequence/define.d.ts +57 -0
- package/dist/core/sequence/define.js +10 -0
- package/dist/core/sequence/index.d.ts +63 -0
- package/dist/core/sequence/index.js +231 -0
- package/dist/core/slot/binding.d.ts +40 -0
- package/dist/core/slot/binding.js +2 -0
- package/dist/core/slot/company-metadata.d.ts +113 -0
- package/dist/core/slot/company-metadata.js +563 -0
- package/dist/core/slot/compile-schema.d.ts +19 -13
- package/dist/core/slot/compile-schema.js +51 -248
- package/dist/core/slot/contact-timezone.d.ts +110 -0
- package/dist/core/slot/contact-timezone.js +186 -0
- package/dist/core/slot/field.d.ts +2 -24
- package/dist/core/slot/field.js +0 -69
- package/dist/core/slot/id-ref.d.ts +17 -2
- package/dist/core/slot/id-ref.js +16 -8
- package/dist/core/slot/index.d.ts +12 -13
- package/dist/core/slot/index.js +11 -12
- package/dist/core/slot/naming.d.ts +15 -0
- package/dist/core/slot/naming.js +96 -0
- package/dist/core/slot/platform-fields.d.ts +119 -0
- package/dist/core/slot/platform-fields.js +121 -0
- package/dist/core/slot/ref.d.ts +16 -0
- package/dist/core/slot/ref.js +14 -0
- package/dist/core/slot/row.d.ts +2 -9
- package/dist/core/slot/row.js +13 -23
- package/dist/core/slot/schema-compat.d.ts +16 -0
- package/dist/core/slot/schema-compat.js +112 -0
- package/dist/core/slot/schema.d.ts +409 -76
- package/dist/core/slot/schema.js +178 -10
- package/dist/core/slot/types.d.ts +27 -94
- package/dist/core/slot/value.d.ts +26 -3
- package/dist/core/slot/value.js +89 -57
- package/dist/dev-overlay/protocol.d.ts +41 -8
- package/dist/fn-context/build-context.d.ts +3 -1
- package/dist/fn-context/build-context.js +20 -10
- package/dist/fn-context/fake/empty-api.js +9 -28
- package/dist/fn-context/fake/index.d.ts +11 -4
- package/dist/fn-context/fake/index.js +27 -2
- package/dist/fn-context/fn-params.d.ts +6 -0
- package/dist/fn-context/fn-params.js +13 -0
- package/dist/fn-context/index.d.ts +5 -5
- package/dist/fn-context/index.js +4 -4
- package/dist/fn-context/log.js +2 -2
- package/dist/fn-context/make-ctx.d.ts +11 -0
- package/dist/fn-context/make-ctx.js +18 -8
- package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +24 -0
- package/dist/fn-context/rpc/case-orchestrator-tools.js +13 -0
- package/dist/fn-context/rpc/index.d.ts +3 -1
- package/dist/fn-context/rpc/index.js +3 -1
- package/dist/fn-context/rpc/integration/index.d.ts +4 -4
- package/dist/fn-context/rpc/integration/index.js +1 -1
- package/dist/fn-context/rpc/integration/list-surfaces.d.ts +21 -0
- package/dist/fn-context/rpc/integration/{list-bindings.js → list-surfaces.js} +4 -4
- package/dist/fn-context/rpc/platform.d.ts +2 -2
- package/dist/fn-context/rpc/platform.js +1 -1
- package/dist/fn-context/rpc/records.d.ts +78 -0
- package/dist/fn-context/rpc/records.js +26 -0
- package/dist/fn-context/rpc/trpc-client.d.ts +2 -2
- package/dist/fn-context/rpc/trpc-client.js +88 -7
- package/dist/fn-context/rpc/trpc-init.d.ts +2 -2
- package/dist/fn-context/rpc/trpc-router-factory.d.ts +49 -3
- package/dist/fn-context/rpc/trpc-router-factory.js +2 -0
- package/dist/fn-context/run-scope.d.ts +4 -0
- package/dist/fn-context/run-scope.js +9 -0
- package/dist/fn-context/types.d.ts +36 -3
- package/dist/index.d.ts +37 -46
- package/dist/index.js +33 -33
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +41 -0
- package/dist/platform.d.ts +76 -0
- package/dist/platform.js +56 -0
- package/dist/voice-service/actions.d.ts +71 -4
- package/dist/voice-service/types.d.ts +42 -5
- package/dist/worker.d.ts +10 -0
- package/dist/worker.js +10 -0
- package/package.json +38 -19
- package/dist/builder/compile/compile-agenda.d.ts +0 -15
- package/dist/builder/compile/compile-agenda.js +0 -64
- package/dist/builder/merge.d.ts +0 -4
- package/dist/builder/merge.js +0 -19
- package/dist/compute-host/read-only-cache.d.ts +0 -20
- package/dist/compute-host/read-only-cache.js +0 -61
- package/dist/core/context/db.d.ts +0 -69
- package/dist/core/context/db.js +0 -44
- package/dist/core/context/docusign.d.ts +0 -134
- package/dist/core/context/docusign.js +0 -108
- package/dist/core/context/filevine.d.ts +0 -323
- package/dist/core/context/filevine.js +0 -254
- package/dist/core/context/http.d.ts +0 -9
- package/dist/core/context/http.js +0 -4
- package/dist/core/context/hubspot.d.ts +0 -250
- package/dist/core/context/hubspot.js +0 -191
- package/dist/core/context/twilio.d.ts +0 -50
- package/dist/core/context/twilio.js +0 -42
- package/dist/core/functions/define/case-context.d.ts +0 -14
- package/dist/core/functions/define/case-context.js +0 -15
- package/dist/core/functions/define/compute.d.ts +0 -7
- package/dist/core/functions/define/compute.js +0 -17
- package/dist/core/functions/define/custom.d.ts +0 -4
- package/dist/core/functions/define/custom.js +0 -8
- package/dist/core/functions/define/system.d.ts +0 -7
- package/dist/core/functions/define/system.js +0 -12
- package/dist/core/slot/deps.d.ts +0 -15
- package/dist/core/slot/deps.js +0 -88
- package/dist/core/slot/derived.d.ts +0 -32
- package/dist/core/slot/derived.js +0 -115
- package/dist/core/slot/platform-session-fields.d.ts +0 -38
- package/dist/core/slot/platform-session-fields.js +0 -48
- package/dist/core/slot/sf-schema.d.ts +0 -23
- package/dist/core/slot/sf-schema.js +0 -125
- package/dist/core/slot/sf-validate.d.ts +0 -14
- package/dist/core/slot/sf-validate.js +0 -132
- package/dist/core/slot/snapshot.d.ts +0 -18
- package/dist/core/slot/snapshot.js +0 -138
- package/dist/core/slot/write.d.ts +0 -48
- package/dist/core/slot/write.js +0 -426
- package/dist/core/stage/builders.d.ts +0 -54
- package/dist/core/stage/builders.js +0 -31
- package/dist/core/stage/condition.d.ts +0 -17
- package/dist/core/stage/condition.js +0 -198
- package/dist/core/stage/drive.d.ts +0 -43
- package/dist/core/stage/drive.js +0 -78
- package/dist/core/stage/finish-when.d.ts +0 -20
- package/dist/core/stage/finish-when.js +0 -82
- package/dist/core/stage/flatten.d.ts +0 -27
- package/dist/core/stage/flatten.js +0 -466
- package/dist/core/stage/index.d.ts +0 -15
- package/dist/core/stage/index.js +0 -13
- package/dist/core/stage/interrupts.d.ts +0 -28
- package/dist/core/stage/interrupts.js +0 -120
- package/dist/core/stage/ivr-fields.d.ts +0 -9
- package/dist/core/stage/ivr-fields.js +0 -75
- package/dist/core/stage/simulate.d.ts +0 -24
- package/dist/core/stage/simulate.js +0 -33
- package/dist/core/stage/transfer-fields.d.ts +0 -19
- package/dist/core/stage/transfer-fields.js +0 -129
- package/dist/core/stage/transfer-spec.d.ts +0 -48
- package/dist/core/stage/transfer-spec.js +0 -113
- package/dist/core/stage/types.d.ts +0 -208
- package/dist/core/stage/types.js +0 -2
- package/dist/core/stage/validate.d.ts +0 -11
- package/dist/core/stage/validate.js +0 -330
- package/dist/core/stage/walk.d.ts +0 -39
- package/dist/core/stage/walk.js +0 -119
- package/dist/fn-context/rpc/integration/list-bindings.d.ts +0 -21
- package/dist/fn-context/system-args.d.ts +0 -6
- package/dist/fn-context/system-args.js +0 -14
package/dist/cli/serve/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { cancel, isCancel, select } from '@clack/prompts';
|
|
2
3
|
import chokidar from 'chokidar';
|
|
3
|
-
import { compileAll } from '../../builder/compile/compile-sequence.js';
|
|
4
|
+
import { compileAll, compileCompanyMetadataFromRepo, compileCompanyPart, runtimePackageSource, runtimePackageVersion, } from '../../builder/compile/compile-sequence.js';
|
|
4
5
|
import { withCompanyImportGraph } from '../../builder/company-import-graph.js';
|
|
5
|
-
import { findTsModule } from '../../builder/paths.js';
|
|
6
|
-
import { listSequenceIds,
|
|
6
|
+
import { findTsModule, requireCompanyResolveModule, requireResolveModule } from '../../builder/paths.js';
|
|
7
|
+
import { listSequenceIds, loadCompanyWebhook, loadSequenceCaseOrchestrator, WEBHOOK_MODULE, loadCompanyResolver, loadSequenceResolver, loadSequenceEvents, loadSequenceFunctions, } from '../../builder/load.js';
|
|
8
|
+
import { loadSchedules } from '../../builder/schedules.js';
|
|
9
|
+
import { ORCHESTRATOR_CONTACTS_FUNCTION, ORCHESTRATOR_FUNCTION } from '../../core/case-orchestrator/types.js';
|
|
10
|
+
import { WEBHOOK_RECEIVED_FUNCTION } from '../../core/functions/events.js';
|
|
11
|
+
import { COMPANY_RESOLVE_FUNCTION, RESOLVE_FUNCTION } from '../../core/party/types.js';
|
|
12
|
+
import { COMPANY_SCOPE } from '../../builder/types.js';
|
|
7
13
|
import { createSourceComputeHost } from '../../compute-host/source-compute-host.js';
|
|
8
14
|
import { formatRuntimeVersion } from '../../fn-context/rpc/request-id.js';
|
|
9
15
|
import { encodeOverlayMessage, OVERLAY_REPLACED_CLOSE_CODE, parseOverlayMessage, } from '../../dev-overlay/protocol.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
16
|
+
import { getLogger } from '../../logger.js';
|
|
17
|
+
import { listServeEnvironments, loadCachedServeAuthorization, resolveBackendUrl, ServeAuthorizationRejectedError, } from '../auth/auth.js';
|
|
18
|
+
import { forgetServeAuthorization, loginServe } from '../auth/serve-login.js';
|
|
19
|
+
import { toHttpUrl, toWsUrl } from '../auth/backend-url.js';
|
|
20
|
+
import { progressIndicator } from '../auth/progress.js';
|
|
21
|
+
const logger = getLogger(['runtime', 'overlay']);
|
|
15
22
|
export function overlayWebSocketUrl(raw) {
|
|
16
|
-
const url =
|
|
17
|
-
if (url.protocol === 'http:')
|
|
18
|
-
url.protocol = 'ws:';
|
|
19
|
-
else if (url.protocol === 'https:')
|
|
20
|
-
url.protocol = 'wss:';
|
|
21
|
-
else if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {
|
|
22
|
-
throw new Error(`unsupported backend url: ${raw}`);
|
|
23
|
-
}
|
|
23
|
+
const url = toWsUrl(raw);
|
|
24
24
|
const path = url.pathname.replace(/\/$/, '');
|
|
25
25
|
if (path === '')
|
|
26
26
|
url.pathname = '/overlay';
|
|
@@ -29,14 +29,7 @@ export function overlayWebSocketUrl(raw) {
|
|
|
29
29
|
return url.toString();
|
|
30
30
|
}
|
|
31
31
|
function overlayRpcUrl(raw) {
|
|
32
|
-
const url =
|
|
33
|
-
if (url.protocol === 'ws:')
|
|
34
|
-
url.protocol = 'http:';
|
|
35
|
-
else if (url.protocol === 'wss:')
|
|
36
|
-
url.protocol = 'https:';
|
|
37
|
-
else if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
38
|
-
throw new Error(`unsupported backend url: ${raw}`);
|
|
39
|
-
}
|
|
32
|
+
const url = toHttpUrl(raw);
|
|
40
33
|
url.pathname = `${url.pathname.replace(/\/$/, '')}/trpc`;
|
|
41
34
|
url.search = '';
|
|
42
35
|
url.hash = '';
|
|
@@ -48,9 +41,20 @@ function sequenceIdFromPath(repoRoot, file) {
|
|
|
48
41
|
const match = /^sequences[/\\]([^/\\]+)(?:[/\\]|$)/.exec(rel);
|
|
49
42
|
return match?.[1] ?? null;
|
|
50
43
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
/** Not the repository's code: dependencies, git, and what compiling writes. */
|
|
45
|
+
export function isIgnoredServePath(repoRoot, file) {
|
|
46
|
+
const parts = path.relative(repoRoot, file).split(/[/\\]/);
|
|
47
|
+
return parts.some((part) => part === 'node_modules' || part === '__gen' || part === 'build' || part === 'dist' || part.startsWith('.'));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What a changed file recompiles (22-sequence-as-business-unit §6): a file of one sequence, that
|
|
51
|
+
* sequence; anything else (`schema/`, `resolve.ts`, shared code) can reach every sequence, so all.
|
|
52
|
+
*/
|
|
53
|
+
export function reloadScopeForPath(repoRoot, file) {
|
|
54
|
+
if (isIgnoredServePath(repoRoot, file))
|
|
55
|
+
return null;
|
|
56
|
+
const sequenceId = sequenceIdFromPath(repoRoot, file);
|
|
57
|
+
return sequenceId ? { sequenceId } : 'all';
|
|
54
58
|
}
|
|
55
59
|
async function loadOne(repoRoot, sequenceId, generation) {
|
|
56
60
|
return withCompanyImportGraph(repoRoot, async () => {
|
|
@@ -67,26 +71,110 @@ async function loadOne(repoRoot, sequenceId, generation) {
|
|
|
67
71
|
if (!functionsPath) {
|
|
68
72
|
throw new Error(`No functions module for sequence ${sequenceId}`);
|
|
69
73
|
}
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
+
const caseOrchestratorPath = findTsModule(sequenceDir, 'orchestrator');
|
|
75
|
+
const authored = await loadSequenceFunctions(functionsPath);
|
|
76
|
+
const functions = {
|
|
77
|
+
...authored,
|
|
78
|
+
[RESOLVE_FUNCTION]: await loadSequenceResolver(requireResolveModule(sequenceDir, sequenceId)),
|
|
79
|
+
...(caseOrchestratorPath ? await caseOrchestratorFunctions(caseOrchestratorPath) : {}),
|
|
80
|
+
// The jobs of its schedules.ts run here too while serving (25 §6.4).
|
|
81
|
+
...(await loadSchedules(sequenceDir, `sequences/${sequenceId}/schedules.ts`))?.functions,
|
|
82
|
+
};
|
|
83
|
+
const eventsPath = findTsModule(sequenceDir, 'events');
|
|
84
|
+
const events = eventsPath ? await loadSequenceEvents(eventsPath) : {};
|
|
85
|
+
return { build: compiled.build, functions, events, generation };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function loadCompany(repoRoot) {
|
|
89
|
+
return withCompanyImportGraph(repoRoot, async () => {
|
|
90
|
+
const webhookPath = findTsModule(repoRoot, WEBHOOK_MODULE);
|
|
91
|
+
return {
|
|
92
|
+
part: await compileCompanyPart(repoRoot, { includeCode: false }),
|
|
93
|
+
companyMetadata: await compileCompanyMetadataFromRepo(repoRoot),
|
|
94
|
+
functions: {
|
|
95
|
+
[COMPANY_RESOLVE_FUNCTION]: await loadCompanyResolver(requireCompanyResolveModule(repoRoot)),
|
|
96
|
+
...(webhookPath ? { [WEBHOOK_RECEIVED_FUNCTION]: await loadCompanyWebhook(webhookPath) } : {}),
|
|
97
|
+
...(await loadSchedules(repoRoot, 'schedules.ts'))?.functions,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
74
100
|
});
|
|
75
101
|
}
|
|
102
|
+
/** orchestrator.ts's two functions, as the build names them. */
|
|
103
|
+
async function caseOrchestratorFunctions(path) {
|
|
104
|
+
const { orchestrator, contacts } = await loadSequenceCaseOrchestrator(path);
|
|
105
|
+
return {
|
|
106
|
+
[ORCHESTRATOR_FUNCTION]: { kind: 'orchestrator', main: orchestrator },
|
|
107
|
+
[ORCHESTRATOR_CONTACTS_FUNCTION]: { kind: 'orchestrator', main: contacts },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The repository's sequences, compiled; a listed sequence missing from the repository is left out. */
|
|
76
111
|
async function loadSequences(repoRoot, sequenceIds, generation) {
|
|
77
112
|
return withCompanyImportGraph(repoRoot, async () => {
|
|
113
|
+
const present = new Set(listSequenceIds(repoRoot));
|
|
78
114
|
const out = new Map();
|
|
79
115
|
for (const sequenceId of sequenceIds) {
|
|
80
|
-
|
|
116
|
+
if (present.has(sequenceId))
|
|
117
|
+
out.set(sequenceId, await loadOne(repoRoot, sequenceId, generation));
|
|
81
118
|
}
|
|
82
119
|
return out;
|
|
83
120
|
});
|
|
84
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The changes waiting to be reloaded. A reload that fails keeps its scope (`fail`) for the next one,
|
|
124
|
+
* which a new file change starts (no retry on its own: the failure is in the code until it is
|
|
125
|
+
* edited); it is cleared only once a reload of it loads.
|
|
126
|
+
*/
|
|
127
|
+
export class ReloadQueue {
|
|
128
|
+
pending = { all: false, ids: new Set() };
|
|
129
|
+
failed = { all: false, ids: new Set() };
|
|
130
|
+
add(scope) {
|
|
131
|
+
if (scope === 'all')
|
|
132
|
+
this.pending.all = true;
|
|
133
|
+
else
|
|
134
|
+
this.pending.ids.add(scope.sequenceId);
|
|
135
|
+
}
|
|
136
|
+
/** Something changed since the last reload. */
|
|
137
|
+
hasChanges() {
|
|
138
|
+
return this.pending.all || this.pending.ids.size > 0;
|
|
139
|
+
}
|
|
140
|
+
/** The next reload: the new changes plus what failed before. */
|
|
141
|
+
take() {
|
|
142
|
+
const all = this.pending.all || this.failed.all;
|
|
143
|
+
const job = { all, ids: all ? new Set() : new Set([...this.failed.ids, ...this.pending.ids]) };
|
|
144
|
+
this.pending = { all: false, ids: new Set() };
|
|
145
|
+
this.failed = { all: false, ids: new Set() };
|
|
146
|
+
return job;
|
|
147
|
+
}
|
|
148
|
+
fail(job) {
|
|
149
|
+
this.failed = { all: this.failed.all || job.all, ids: new Set([...this.failed.ids, ...job.ids]) };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The state after a reload (`all`: the whole repository; else the sequences `ids` names), built
|
|
154
|
+
* aside: if any part fails to load it throws and the current state stays whole, never new company
|
|
155
|
+
* code with old sequences.
|
|
156
|
+
*/
|
|
157
|
+
export async function reloadServeState(repoRoot, current, job, generation) {
|
|
158
|
+
// Recompiling everything also picks up sequence directories added or removed since.
|
|
159
|
+
const ids = job.all ? listSequenceIds(repoRoot) : [...job.ids];
|
|
160
|
+
// The company's code is outside `sequences/`, so only a recompile of everything reaches it.
|
|
161
|
+
const company = job.all ? await loadCompany(repoRoot) : current.company;
|
|
162
|
+
const loaded = await loadSequences(repoRoot, ids, generation);
|
|
163
|
+
const sequences = job.all ? new Map() : new Map(current.sequences);
|
|
164
|
+
for (const id of ids) {
|
|
165
|
+
const next = loaded.get(id);
|
|
166
|
+
if (next)
|
|
167
|
+
sequences.set(id, next);
|
|
168
|
+
else
|
|
169
|
+
sequences.delete(id);
|
|
170
|
+
}
|
|
171
|
+
return { company, sequences };
|
|
172
|
+
}
|
|
85
173
|
function send(ws, message) {
|
|
86
174
|
ws.send(encodeOverlayMessage(message));
|
|
87
175
|
}
|
|
88
176
|
function logField(label, value) {
|
|
89
|
-
|
|
177
|
+
logger.info(label, value);
|
|
90
178
|
}
|
|
91
179
|
async function handleExecute(loaded, message, rpcUrl) {
|
|
92
180
|
const data = message.data
|
|
@@ -97,30 +185,31 @@ async function handleExecute(loaded, message, rpcUrl) {
|
|
|
97
185
|
: undefined;
|
|
98
186
|
const host = createSourceComputeHost({
|
|
99
187
|
functions: loaded.functions,
|
|
100
|
-
|
|
188
|
+
events: loaded.events,
|
|
101
189
|
data: data,
|
|
102
190
|
runToken: message.runToken,
|
|
103
|
-
runtimeVersion: loaded.
|
|
104
|
-
runtimeSource: loaded.
|
|
191
|
+
runtimeVersion: loaded.runtimeVersion,
|
|
192
|
+
runtimeSource: loaded.runtimeSource,
|
|
105
193
|
getRequestId: () => message.logRequestId,
|
|
106
194
|
getTags: () => message.logTags,
|
|
107
195
|
});
|
|
108
196
|
try {
|
|
109
|
-
return await host.execute(message.name, message.
|
|
197
|
+
return await host.execute(message.name, message.invocation);
|
|
110
198
|
}
|
|
111
199
|
finally {
|
|
112
200
|
host.dispose();
|
|
113
201
|
}
|
|
114
202
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
203
|
+
/**
|
|
204
|
+
* Push the local company build: the company's functions and every loaded sequence, as one overlay.
|
|
205
|
+
* With no sequence left it is still pushed, so the environment drops the ones it had (22 §11).
|
|
206
|
+
*/
|
|
207
|
+
export function pushOverlay(ws, company, sequences, generation) {
|
|
208
|
+
const build = {
|
|
209
|
+
company: company.part,
|
|
210
|
+
sequences: Object.fromEntries([...sequences].map(([sequenceId, sequence]) => [sequenceId, sequence.build])),
|
|
211
|
+
};
|
|
212
|
+
send(ws, { type: 'overlay', generation, build, companyMetadata: company.companyMetadata });
|
|
124
213
|
}
|
|
125
214
|
function closeDetail(event) {
|
|
126
215
|
return event.reason ? `${event.code} ${event.reason}` : String(event.code);
|
|
@@ -160,6 +249,36 @@ class OverlayReplacedError extends Error {
|
|
|
160
249
|
this.name = 'OverlayReplacedError';
|
|
161
250
|
}
|
|
162
251
|
}
|
|
252
|
+
/** Transient: an infra hiccup, or a lease that was just replaced. Retry with backoff. */
|
|
253
|
+
class OverlayUnavailableError extends Error {
|
|
254
|
+
constructor(message) {
|
|
255
|
+
super(message);
|
|
256
|
+
this.name = 'OverlayUnavailableError';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** Permanent: the request itself is invalid (missing or unknown environment, prod). Fatal. */
|
|
260
|
+
class OverlayRequestError extends Error {
|
|
261
|
+
constructor(message) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.name = 'OverlayRequestError';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
export function toOverlayError(message) {
|
|
267
|
+
switch (message.code) {
|
|
268
|
+
case 'authorization':
|
|
269
|
+
return new ServeAuthorizationRejectedError(message.error);
|
|
270
|
+
case 'request':
|
|
271
|
+
return new OverlayRequestError(message.error);
|
|
272
|
+
case 'unavailable':
|
|
273
|
+
return new OverlayUnavailableError(message.error);
|
|
274
|
+
default: {
|
|
275
|
+
// The wire format is not type-checked: a code this CLI doesn't know is a protocol bug, so
|
|
276
|
+
// stop and say so instead of reconnecting forever on an `undefined` error.
|
|
277
|
+
const unknown = message.code;
|
|
278
|
+
return new OverlayRequestError(`unknown auth.error code ${JSON.stringify(unknown)}: ${message.error}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
163
282
|
function waitAuthOk(ws) {
|
|
164
283
|
return new Promise((resolve, reject) => {
|
|
165
284
|
const onMessage = (event) => {
|
|
@@ -177,7 +296,7 @@ function waitAuthOk(ws) {
|
|
|
177
296
|
}
|
|
178
297
|
if (message.type === 'auth.error') {
|
|
179
298
|
cleanup();
|
|
180
|
-
reject(
|
|
299
|
+
reject(toOverlayError(message));
|
|
181
300
|
}
|
|
182
301
|
}
|
|
183
302
|
catch (error) {
|
|
@@ -199,7 +318,7 @@ function waitAuthOk(ws) {
|
|
|
199
318
|
ws.addEventListener('close', onClose);
|
|
200
319
|
});
|
|
201
320
|
}
|
|
202
|
-
function attachExecuteHandler(ws,
|
|
321
|
+
function attachExecuteHandler(ws, find, rpcUrl) {
|
|
203
322
|
ws.addEventListener('message', (event) => {
|
|
204
323
|
void (async () => {
|
|
205
324
|
let message;
|
|
@@ -207,46 +326,72 @@ function attachExecuteHandler(ws, sequences, rpcUrl) {
|
|
|
207
326
|
message = parseOverlayMessage(String(event.data));
|
|
208
327
|
}
|
|
209
328
|
catch (error) {
|
|
210
|
-
|
|
329
|
+
logger.error('bad server message', error);
|
|
211
330
|
return;
|
|
212
331
|
}
|
|
213
332
|
if (message.type !== 'execute')
|
|
214
333
|
return;
|
|
215
|
-
const
|
|
216
|
-
const loaded = sequences.get(message.sequenceId);
|
|
334
|
+
const loaded = find(message.sequenceId);
|
|
217
335
|
if (!loaded) {
|
|
218
336
|
const error = `unknown sequence ${message.sequenceId}`;
|
|
219
|
-
console.error(`execute ${call} failed: ${error}`);
|
|
220
337
|
send(ws, {
|
|
221
338
|
type: 'execute.result',
|
|
222
339
|
requestId: message.requestId,
|
|
223
340
|
ok: false,
|
|
224
341
|
error,
|
|
225
342
|
});
|
|
343
|
+
logger.error('execute failed', { sequenceId: message.sequenceId, name: message.name, error });
|
|
226
344
|
return;
|
|
227
345
|
}
|
|
228
346
|
const started = Date.now();
|
|
229
|
-
|
|
347
|
+
let result;
|
|
230
348
|
try {
|
|
231
|
-
|
|
232
|
-
console.log(`execute ${call} ok ${Date.now() - started}ms`);
|
|
233
|
-
send(ws, { type: 'execute.result', requestId: message.requestId, ok: true, result });
|
|
349
|
+
result = await handleExecute(loaded, message, rpcUrl);
|
|
234
350
|
}
|
|
235
351
|
catch (error) {
|
|
236
352
|
const text = error instanceof Error ? error.message : String(error);
|
|
237
|
-
console.error(`execute ${call} failed: ${text}`);
|
|
238
353
|
send(ws, {
|
|
239
354
|
type: 'execute.result',
|
|
240
355
|
requestId: message.requestId,
|
|
241
356
|
ok: false,
|
|
242
357
|
error: text,
|
|
243
358
|
});
|
|
359
|
+
logger.error('execute failed', {
|
|
360
|
+
sequenceId: message.sequenceId,
|
|
361
|
+
name: message.name,
|
|
362
|
+
durationMs: Date.now() - started,
|
|
363
|
+
error: text,
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
244
366
|
}
|
|
367
|
+
send(ws, { type: 'execute.result', requestId: message.requestId, ok: true, result });
|
|
368
|
+
logger.info('execute ok', {
|
|
369
|
+
sequenceId: message.sequenceId,
|
|
370
|
+
name: message.name,
|
|
371
|
+
durationMs: Date.now() - started,
|
|
372
|
+
});
|
|
245
373
|
})();
|
|
246
374
|
});
|
|
247
375
|
}
|
|
376
|
+
function attachNoticeHandler(ws) {
|
|
377
|
+
ws.addEventListener('message', (event) => {
|
|
378
|
+
let message;
|
|
379
|
+
try {
|
|
380
|
+
message = parseOverlayMessage(String(event.data));
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (message.type !== 'notice')
|
|
386
|
+
return;
|
|
387
|
+
if (message.details.length > 0)
|
|
388
|
+
logger.warn(message.message, message.details);
|
|
389
|
+
else
|
|
390
|
+
logger.warn(message.message);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
248
393
|
function waitSessionEnd(ws) {
|
|
249
|
-
return new Promise((resolve) => {
|
|
394
|
+
return new Promise((resolve, reject) => {
|
|
250
395
|
if (ws.readyState === WebSocket.CLOSED) {
|
|
251
396
|
resolve({ closed: 'already closed' });
|
|
252
397
|
return;
|
|
@@ -255,9 +400,14 @@ function waitSessionEnd(ws) {
|
|
|
255
400
|
try {
|
|
256
401
|
const message = parseOverlayMessage(String(event.data));
|
|
257
402
|
if (message.type === 'replaced') {
|
|
403
|
+
logger.warn('another serve took the environment');
|
|
258
404
|
cleanup();
|
|
259
405
|
resolve('replaced');
|
|
260
406
|
}
|
|
407
|
+
else if (message.type === 'auth.error') {
|
|
408
|
+
cleanup();
|
|
409
|
+
reject(toOverlayError(message));
|
|
410
|
+
}
|
|
261
411
|
}
|
|
262
412
|
catch {
|
|
263
413
|
return;
|
|
@@ -275,35 +425,128 @@ function waitSessionEnd(ws) {
|
|
|
275
425
|
ws.addEventListener('close', onClose);
|
|
276
426
|
});
|
|
277
427
|
}
|
|
428
|
+
function exitOnCancel() {
|
|
429
|
+
cancel('Cancelled.');
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Only prompt when there's an actual person at a terminal to answer — a CI runner, a piped/
|
|
434
|
+
* redirected stdin, or any other non-interactive invocation must keep failing fast on a missing
|
|
435
|
+
* flag instead of hanging on a readline prompt with undefined behavior off a real TTY.
|
|
436
|
+
*/
|
|
437
|
+
function requireInteractiveOrThrow(flag) {
|
|
438
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
439
|
+
throw new Error(`${flag} is required (not running in an interactive terminal to prompt for it)`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** The token's non-prod environments. A rejected token surfaces as `ServeAuthorizationRejectedError`. */
|
|
443
|
+
async function fetchEnvironments(backendUrl, authorization) {
|
|
444
|
+
const progress = progressIndicator();
|
|
445
|
+
const company = authorization.companyName ? ` in ${authorization.companyName}` : '';
|
|
446
|
+
progress.start(`Fetching environments${company}…`);
|
|
447
|
+
let environments;
|
|
448
|
+
try {
|
|
449
|
+
environments = await listServeEnvironments(backendUrl, authorization.token);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (error instanceof ServeAuthorizationRejectedError) {
|
|
453
|
+
progress.error('Login was rejected by the backend.');
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
progress.error('Could not fetch environments.');
|
|
457
|
+
throw new Error(`Could not list environments: ${error instanceof Error ? error.message : String(error)}`);
|
|
458
|
+
}
|
|
459
|
+
progress.stop(`Found ${environments.length} environment${environments.length === 1 ? '' : 's'}${company}.`);
|
|
460
|
+
return environments;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Login plus the company's environments. Listing them doubles as checking the cached token against
|
|
464
|
+
* the backend: it can be unexpired locally yet rejected (revoked, re-keyed, access removed), and
|
|
465
|
+
* then it is dropped for a fresh browser login. Rejections after startup stop serve instead.
|
|
466
|
+
*/
|
|
467
|
+
async function authorizeWithEnvironments(backendUrl, openBrowser) {
|
|
468
|
+
const cached = await loadCachedServeAuthorization(backendUrl);
|
|
469
|
+
if (cached) {
|
|
470
|
+
logger.info('using cached serve authorization');
|
|
471
|
+
try {
|
|
472
|
+
return { authorization: cached, environments: await fetchEnvironments(backendUrl, cached) };
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
if (!(error instanceof ServeAuthorizationRejectedError))
|
|
476
|
+
throw error;
|
|
477
|
+
logger.warn('cached serve authorization is no longer valid; authorizing again', { error: error.message });
|
|
478
|
+
await forgetServeAuthorization(backendUrl, cached);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const authorization = await loginServe(backendUrl, { openBrowser });
|
|
482
|
+
return { authorization, environments: await fetchEnvironments(backendUrl, authorization) };
|
|
483
|
+
}
|
|
484
|
+
/** Prompts for one non-prod environment when `--environment` was not given. Shows id and name. */
|
|
485
|
+
async function promptForEnvironment(environments) {
|
|
486
|
+
if (environments.length === 0) {
|
|
487
|
+
throw new Error('No non-prod environments available for this company.');
|
|
488
|
+
}
|
|
489
|
+
const picked = await select({
|
|
490
|
+
message: 'Which environment should Local Serve overlay?',
|
|
491
|
+
options: environments.map((environment) => ({
|
|
492
|
+
value: environment.id,
|
|
493
|
+
label: environment.name,
|
|
494
|
+
hint: environment.id,
|
|
495
|
+
})),
|
|
496
|
+
});
|
|
497
|
+
if (isCancel(picked))
|
|
498
|
+
exitOnCancel();
|
|
499
|
+
return picked;
|
|
500
|
+
}
|
|
278
501
|
export async function runServe(repoRoot, options = {}) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (!
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const rpcUrl = overlayRpcUrl(backendUrl);
|
|
288
|
-
const sequenceIds = options.sequenceIds ?? listSequenceIds(repoRoot);
|
|
289
|
-
if (sequenceIds.length === 0) {
|
|
290
|
-
throw new Error(`No sequences with slots found under ${path.join(repoRoot, 'sequences')}`);
|
|
502
|
+
const backendUrl = resolveBackendUrl(repoRoot, options.url);
|
|
503
|
+
// A non-interactive caller cannot answer the prompt. Check this before authorization so a CI job
|
|
504
|
+
// with a missing flag fails instead of waiting on a browser-based device-auth flow.
|
|
505
|
+
let environment = options.environment?.trim();
|
|
506
|
+
if (!environment)
|
|
507
|
+
requireInteractiveOrThrow('--environment <id>');
|
|
508
|
+
if (listSequenceIds(repoRoot).length === 0) {
|
|
509
|
+
throw new Error(`No sequences found under ${path.join(repoRoot, 'sequences')}`);
|
|
291
510
|
}
|
|
511
|
+
// Login is the first interactive step: the environment prompt needs the company-scoped token.
|
|
512
|
+
const { authorization, environments } = await authorizeWithEnvironments(backendUrl, options.openBrowser !== false);
|
|
513
|
+
if (!environment)
|
|
514
|
+
environment = await promptForEnvironment(environments);
|
|
515
|
+
// The whole repository serves the environment (22 §6): every sequence, and only these.
|
|
292
516
|
let generation = 1;
|
|
293
|
-
|
|
517
|
+
let company = await loadCompany(repoRoot);
|
|
518
|
+
let sequences = await loadSequences(repoRoot, listSequenceIds(repoRoot), generation);
|
|
519
|
+
const runtimeSource = runtimePackageSource(repoRoot);
|
|
520
|
+
const find = (scope) => {
|
|
521
|
+
if (scope === COMPANY_SCOPE) {
|
|
522
|
+
return { functions: company.functions, events: {}, runtimeVersion: runtimePackageVersion(), runtimeSource };
|
|
523
|
+
}
|
|
524
|
+
const loaded = sequences.get(scope);
|
|
525
|
+
return loaded
|
|
526
|
+
? {
|
|
527
|
+
functions: loaded.functions,
|
|
528
|
+
events: loaded.events,
|
|
529
|
+
runtimeVersion: loaded.build.engineVersion,
|
|
530
|
+
runtimeSource: loaded.build.runtimeSource,
|
|
531
|
+
}
|
|
532
|
+
: undefined;
|
|
533
|
+
};
|
|
534
|
+
const url = overlayWebSocketUrl(backendUrl);
|
|
535
|
+
const rpcUrl = overlayRpcUrl(backendUrl);
|
|
294
536
|
let socket = null;
|
|
295
|
-
const names = [...sequences.keys()];
|
|
296
537
|
const firstBuild = sequences.values().next().value?.build;
|
|
297
|
-
|
|
298
|
-
logField('
|
|
299
|
-
logField('
|
|
538
|
+
logger.info('Local overlay');
|
|
539
|
+
logField('user', authorization.user.email);
|
|
540
|
+
logField('company', authorization.companyId);
|
|
541
|
+
logField('environment', environment);
|
|
542
|
+
logField('sequences', [...sequences.keys()].join(', '));
|
|
300
543
|
if (firstBuild) {
|
|
301
|
-
logField('runtime', formatRuntimeVersion(firstBuild.engineVersion, firstBuild.runtimeSource
|
|
544
|
+
logField('runtime', formatRuntimeVersion(firstBuild.engineVersion, firstBuild.runtimeSource));
|
|
302
545
|
}
|
|
303
546
|
logField('backend', backendUrl);
|
|
304
547
|
logField('websocket', url);
|
|
305
548
|
let timer;
|
|
306
|
-
|
|
549
|
+
const queue = new ReloadQueue();
|
|
307
550
|
let flushing = false;
|
|
308
551
|
const flush = () => {
|
|
309
552
|
if (flushing)
|
|
@@ -311,54 +554,51 @@ export async function runServe(repoRoot, options = {}) {
|
|
|
311
554
|
flushing = true;
|
|
312
555
|
void (async () => {
|
|
313
556
|
try {
|
|
314
|
-
while (
|
|
315
|
-
const job =
|
|
316
|
-
pending = { all: false, ids: new Set() };
|
|
557
|
+
while (queue.hasChanges()) {
|
|
558
|
+
const job = queue.take();
|
|
317
559
|
generation += 1;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
sequences
|
|
322
|
-
|
|
323
|
-
|
|
560
|
+
// Swapped in only whole: a failed reload leaves the code that runs (and is pushed) as it was.
|
|
561
|
+
let next;
|
|
562
|
+
try {
|
|
563
|
+
next = await reloadServeState(repoRoot, { company, sequences }, job, generation);
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
// Kept for the next change: fixing one sequence then also reloads what this one covered.
|
|
567
|
+
queue.fail(job);
|
|
568
|
+
logger.error('reload failed', error);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
company = next.company;
|
|
572
|
+
sequences = next.sequences;
|
|
324
573
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
325
|
-
|
|
326
|
-
|
|
574
|
+
pushOverlay(socket, company, sequences, generation);
|
|
575
|
+
logger.info('reloaded sequences', job.all ? 'all' : [...job.ids]);
|
|
327
576
|
}
|
|
328
577
|
}
|
|
329
578
|
}
|
|
330
579
|
catch (error) {
|
|
331
|
-
|
|
580
|
+
logger.error('reload failed', error);
|
|
332
581
|
}
|
|
333
582
|
finally {
|
|
334
583
|
flushing = false;
|
|
335
|
-
if (
|
|
584
|
+
if (queue.hasChanges())
|
|
336
585
|
flush();
|
|
337
586
|
}
|
|
338
587
|
})();
|
|
339
588
|
};
|
|
340
589
|
chokidar
|
|
341
|
-
.watch(
|
|
590
|
+
.watch(repoRoot, {
|
|
342
591
|
ignoreInitial: true,
|
|
343
|
-
ignored: (p) =>
|
|
592
|
+
ignored: (p) => isIgnoredServePath(repoRoot, p),
|
|
344
593
|
})
|
|
345
594
|
.on('all', (event, file) => {
|
|
346
595
|
if (!file)
|
|
347
596
|
return;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (file.includes(`${path.sep}build${path.sep}`))
|
|
597
|
+
const scope = reloadScopeForPath(repoRoot, file);
|
|
598
|
+
if (!scope)
|
|
351
599
|
return;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const sequenceId = sequenceIdFromPath(repoRoot, file);
|
|
355
|
-
if (!schemaHit && !sequenceId)
|
|
356
|
-
return;
|
|
357
|
-
console.log(`changed ${rel}`);
|
|
358
|
-
if (schemaHit)
|
|
359
|
-
pending.all = true;
|
|
360
|
-
else if (sequenceId)
|
|
361
|
-
pending.ids.add(sequenceId);
|
|
600
|
+
logger.info('changed', path.relative(repoRoot, file));
|
|
601
|
+
queue.add(scope);
|
|
362
602
|
clearTimeout(timer);
|
|
363
603
|
timer = setTimeout(flush, 100);
|
|
364
604
|
});
|
|
@@ -369,26 +609,35 @@ export async function runServe(repoRoot, options = {}) {
|
|
|
369
609
|
try {
|
|
370
610
|
await waitOpen(ws);
|
|
371
611
|
const authOk = waitAuthOk(ws);
|
|
372
|
-
send(ws, {
|
|
373
|
-
type: 'auth',
|
|
374
|
-
apiKey,
|
|
375
|
-
companyId,
|
|
376
|
-
names: [...sequences.keys()],
|
|
377
|
-
});
|
|
612
|
+
send(ws, { type: 'auth', token: authorization.token, environment });
|
|
378
613
|
await authOk;
|
|
379
|
-
attachExecuteHandler(ws,
|
|
380
|
-
|
|
614
|
+
attachExecuteHandler(ws, find, rpcUrl);
|
|
615
|
+
attachNoticeHandler(ws);
|
|
616
|
+
pushOverlay(ws, company, sequences, generation);
|
|
381
617
|
delayMs = 500;
|
|
382
|
-
|
|
618
|
+
logger.info('connected', { url });
|
|
383
619
|
const end = await waitSessionEnd(ws);
|
|
384
620
|
if (end === 'replaced')
|
|
385
621
|
throw new OverlayReplacedError();
|
|
386
|
-
|
|
622
|
+
logger.warn('disconnected; retrying', { detail: end.closed });
|
|
387
623
|
}
|
|
388
624
|
catch (error) {
|
|
389
625
|
if (error instanceof OverlayReplacedError)
|
|
390
626
|
throw error;
|
|
391
|
-
|
|
627
|
+
if (error instanceof OverlayRequestError)
|
|
628
|
+
throw error;
|
|
629
|
+
if (error instanceof ServeAuthorizationRejectedError) {
|
|
630
|
+
// Mid-session the identity is fixed: stop rather than log in again, and drop the cached
|
|
631
|
+
// token so the user's next `serve` starts with a fresh login.
|
|
632
|
+
await forgetServeAuthorization(backendUrl, authorization);
|
|
633
|
+
throw new Error(`Serve authorization was rejected (${error.message}). Run serve again to log in.`);
|
|
634
|
+
}
|
|
635
|
+
if (error instanceof OverlayUnavailableError) {
|
|
636
|
+
logger.warn('serve is temporarily unavailable; retrying', { error: error.message });
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
logger.error('connection failed', error);
|
|
640
|
+
}
|
|
392
641
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
393
642
|
ws.close();
|
|
394
643
|
}
|