@astrale-os/sdk 0.5.0-beta.99 → 0.6.0-beta.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 +165 -2
- package/dist/application/action/action.d.ts +7 -4
- package/dist/application/action/authoring.d.ts +2 -1
- package/dist/application/action/context.d.ts +18 -13
- package/dist/application/action/define.d.ts +5 -1
- package/dist/application/action/define.js +6 -1
- package/dist/application/action/index.d.ts +2 -1
- package/dist/application/dependency/index.d.ts +1 -0
- package/dist/application/dependency/invoker.d.ts +26 -0
- package/dist/application/error/error.d.ts +23 -0
- package/dist/application/error/error.js +87 -0
- package/dist/application/error/index.d.ts +2 -0
- package/dist/application/error/index.js +1 -0
- package/dist/application/index.d.ts +2 -2
- package/dist/application/integration/authentication.d.ts +10 -0
- package/dist/application/integration/authentication.typecheck.js +18 -0
- package/dist/application/integration/client.d.ts +2 -0
- package/dist/application/integration/client.js +19 -2
- package/dist/application/integration/index.d.ts +1 -0
- package/dist/application/integration/operation.d.ts +3 -0
- package/dist/application/mutation/authoring/builder.js +2 -2
- package/dist/application/mutation/authoring/endpoint.d.ts +3 -3
- package/dist/application/mutation/authoring/endpoint.js +9 -0
- package/dist/application/mutation/failure.js +13 -7
- package/dist/application/mutation/index.d.ts +1 -0
- package/dist/application/requirements/{profiles.d.ts → core.d.ts} +5 -3
- package/dist/application/requirements/{profiles.js → core.js} +6 -6
- package/dist/application/requirements/index.d.ts +4 -4
- package/dist/application/requirements/index.js +5 -5
- package/dist/application/requirements/requirement.d.ts +3 -2
- package/dist/application/runtime/authoring.d.ts +1 -0
- package/dist/application/runtime/callables.js +2 -2
- package/dist/application/runtime/declarations.d.ts +43 -0
- package/dist/application/runtime/define.d.ts +2 -1
- package/dist/application/view/index.d.ts +1 -1
- package/dist/application/view/route.d.ts +4 -0
- package/dist/application/view/route.js +5 -1
- package/dist/application/workflow/authoring.d.ts +1 -0
- package/dist/application/workflow/context.d.ts +2 -1
- package/dist/application/workflow/define.d.ts +5 -1
- package/dist/application/workflow/define.js +6 -1
- package/dist/application/workflow/index.d.ts +1 -0
- package/dist/application/workflow/runner/runner.d.ts +3 -2
- package/dist/application/workflow/workflow.d.ts +4 -3
- package/dist/deployment/adapter/adapter.d.ts +12 -7
- package/dist/deployment/adapter/define.d.ts +5 -2
- package/dist/deployment/adapter/define.js +15 -17
- package/dist/deployment/adapter/deploy/context.d.ts +6 -0
- package/dist/deployment/adapter/deploy/result.d.ts +2 -0
- package/dist/deployment/adapter/index.d.ts +2 -3
- package/dist/deployment/adapter/index.js +1 -1
- package/dist/deployment/adapter/prepare/context.d.ts +13 -5
- package/dist/deployment/authoring.d.ts +1 -1
- package/dist/deployment/build/compile.js +21 -19
- package/dist/deployment/build/frontend/compilation.d.ts +2 -1
- package/dist/deployment/build/frontend/compilation.js +1 -0
- package/dist/deployment/build/material.js +5 -5
- package/dist/deployment/deploy.d.ts +10 -4
- package/dist/deployment/deploy.js +5 -5
- package/dist/deployment/deployment.d.ts +8 -5
- package/dist/deployment/deployment.js +10 -1
- package/dist/deployment/index.d.ts +5 -4
- package/dist/deployment/index.js +3 -2
- package/dist/deployment/release/addressing.d.ts +6 -2
- package/dist/deployment/release/addressing.js +9 -4
- package/dist/deployment/release/publication/callables.js +1 -1
- package/dist/deployment/release/publication/index.d.ts +1 -1
- package/dist/deployment/release/publication/index.js +1 -1
- package/dist/deployment/release/publication/routes.js +1 -1
- package/dist/deployment/release/publication/seal.d.ts +0 -1
- package/dist/deployment/release/publication/seal.js +4 -5
- package/dist/deployment/release/publication/views.js +2 -1
- package/dist/deployment/verify/preflight/artifact.js +1 -1
- package/dist/deployment/verify/readiness/deployment.js +7 -3
- package/dist/deployment/verify/readiness/identity.js +1 -1
- package/dist/deployment/verify/readiness/publication.js +2 -2
- package/dist/execution/identity/authentication.js +0 -1
- package/dist/execution/identity/exchange.d.ts +1 -0
- package/dist/execution/identity/exchange.js +1 -1
- package/dist/execution/identity/jwks.js +1 -1
- package/dist/execution/invocation/admission/authentication.d.ts +6 -3
- package/dist/execution/invocation/admission/authentication.js +16 -6
- package/dist/execution/invocation/admission/authority.d.ts +2 -2
- package/dist/execution/invocation/dependency/bind.d.ts +5 -0
- package/dist/execution/invocation/dependency/bind.js +68 -0
- package/dist/execution/invocation/dependency/index.d.ts +1 -0
- package/dist/execution/invocation/dependency/index.js +1 -0
- package/dist/execution/invocation/dispatch/action.js +19 -2
- package/dist/execution/invocation/dispatch/context.d.ts +4 -4
- package/dist/execution/invocation/dispatch/context.js +29 -9
- package/dist/execution/invocation/dispatch/workflow.js +17 -4
- package/dist/execution/invocation/integration/authentication.d.ts +5 -0
- package/dist/execution/invocation/integration/authentication.js +53 -0
- package/dist/execution/invocation/integration/call.d.ts +7 -1
- package/dist/execution/invocation/integration/call.js +8 -3
- package/dist/execution/invocation/integration/clients.d.ts +2 -2
- package/dist/execution/invocation/integration/clients.js +2 -2
- package/dist/execution/invocation/lifecycle/active.js +30 -5
- package/dist/execution/invocation/result/errors.d.ts +1 -0
- package/dist/execution/invocation/result/errors.js +15 -10
- package/dist/execution/service/create.js +3 -1
- package/dist/execution/service/service.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/platform/auth/index.d.ts +1 -1
- package/dist/platform/auth/index.js +0 -1
- package/dist/platform/graph/alias.d.ts +6 -0
- package/dist/platform/graph/alias.js +6 -0
- package/dist/platform/graph/document/index.d.ts +2 -1
- package/dist/platform/graph/document/index.js +2 -1
- package/dist/platform/graph/index.d.ts +2 -1
- package/dist/platform/graph/index.js +2 -1
- package/dist/platform/schema/class-authoring.d.ts +37 -0
- package/dist/platform/schema/class-authoring.js +20 -0
- package/dist/platform/schema/class-icon.d.ts +15 -0
- package/dist/platform/schema/class-icon.js +46 -0
- package/dist/platform/schema/error.d.ts +11 -0
- package/dist/platform/schema/error.js +34 -0
- package/dist/platform/schema/errors/index.d.ts +3 -0
- package/dist/platform/schema/errors/index.js +2 -0
- package/dist/platform/schema/icons/index.d.ts +2 -0
- package/dist/platform/schema/icons/index.js +2 -0
- package/dist/platform/schema/index.d.ts +20 -13
- package/dist/platform/schema/index.js +9 -4
- package/dist/platform/schema/kernel.d.ts +22 -0
- package/dist/platform/schema/kernel.js +5 -0
- package/dist/platform/schema/node-id.d.ts +3 -0
- package/dist/platform/schema/node-id.js +6 -0
- package/dist/platform/schema/property.d.ts +12 -0
- package/dist/platform/schema/property.js +3 -0
- package/dist/platform/schema/schema-authoring.d.ts +48 -0
- package/dist/platform/schema/schema-authoring.js +3 -0
- package/dist/platform/schema/state-property.d.ts +11 -0
- package/dist/platform/schema/state-property.js +3 -0
- package/dist/project/admit.d.ts +4 -0
- package/dist/project/admit.js +22 -0
- package/dist/project/define.d.ts +12 -0
- package/dist/project/define.js +106 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +24 -0
- package/dist/project/project.js +1 -0
- package/dist/testing/dataset/admission.d.ts +13 -0
- package/dist/testing/dataset/admission.js +107 -0
- package/dist/testing/dataset/builder.d.ts +42 -0
- package/dist/testing/dataset/builder.js +127 -0
- package/dist/testing/dataset/codec.d.ts +6 -0
- package/dist/testing/dataset/codec.js +113 -0
- package/dist/testing/dataset/dataset.d.ts +58 -0
- package/dist/testing/dataset/dataset.js +2 -0
- package/dist/testing/dataset/define.d.ts +33 -0
- package/dist/testing/dataset/define.js +105 -0
- package/dist/testing/dataset/index.d.ts +8 -0
- package/dist/testing/dataset/index.js +4 -0
- package/dist/testing/dataset/references.d.ts +14 -0
- package/dist/testing/dataset/references.js +56 -0
- package/dist/testing/index.d.ts +9 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/load.d.ts +14 -0
- package/dist/testing/load.js +44 -0
- package/dist/testing/reference.d.ts +11 -0
- package/dist/testing/reference.js +19 -0
- package/dist/testing/tests.d.ts +12 -0
- package/dist/testing/tests.js +31 -0
- package/dist/tooling/cli/arguments.d.ts +7 -4
- package/dist/tooling/cli/arguments.js +82 -107
- package/dist/tooling/cli/astrale/authority.d.ts +5 -0
- package/dist/tooling/cli/astrale/authority.js +11 -0
- package/dist/tooling/cli/astrale/executable.d.ts +2 -0
- package/dist/tooling/cli/astrale/executable.js +47 -0
- package/dist/tooling/cli/astrale/failure.d.ts +6 -0
- package/dist/tooling/cli/astrale/failure.js +48 -0
- package/dist/tooling/cli/astrale/instance.d.ts +11 -0
- package/dist/tooling/cli/astrale/instance.js +52 -0
- package/dist/tooling/cli/astrale/process.d.ts +13 -0
- package/dist/tooling/cli/astrale/process.js +94 -0
- package/dist/tooling/cli/astrale/version.d.ts +18 -0
- package/dist/tooling/cli/astrale/version.js +55 -0
- package/dist/tooling/cli/bun.js +1 -1
- package/dist/tooling/cli/credential.d.ts +2 -0
- package/dist/tooling/cli/credential.js +57 -0
- package/dist/tooling/cli/development/candidate.d.ts +25 -0
- package/dist/tooling/cli/development/candidate.js +176 -0
- package/dist/tooling/cli/development/deployment.d.ts +16 -0
- package/dist/tooling/cli/development/deployment.js +39 -0
- package/dist/tooling/cli/development/develop.d.ts +4 -3
- package/dist/tooling/cli/development/develop.js +125 -289
- package/dist/tooling/cli/development/lease.d.ts +18 -0
- package/dist/tooling/cli/development/lease.js +163 -0
- package/dist/tooling/cli/development/lifecycle.js +4 -0
- package/dist/tooling/cli/development/project-imports.d.ts +22 -0
- package/dist/tooling/cli/development/project-imports.js +39 -0
- package/dist/tooling/cli/development/project-lock.d.ts +3 -4
- package/dist/tooling/cli/development/project-lock.js +14 -166
- package/dist/tooling/cli/development/release-evidence.d.ts +4 -0
- package/dist/tooling/cli/development/release-evidence.js +10 -0
- package/dist/tooling/cli/development/report.d.ts +6 -0
- package/dist/tooling/cli/development/report.js +14 -0
- package/dist/tooling/cli/development/source-watch.js +53 -5
- package/dist/tooling/cli/development/sources.d.ts +7 -0
- package/dist/tooling/cli/development/sources.js +48 -0
- package/dist/tooling/cli/development/target-lock.d.ts +16 -0
- package/dist/tooling/cli/development/target-lock.js +68 -0
- package/dist/tooling/cli/development/view.d.ts +28 -0
- package/dist/tooling/cli/development/view.js +151 -0
- package/dist/tooling/cli/environment-release.d.ts +21 -0
- package/dist/tooling/cli/environment-release.js +46 -0
- package/dist/tooling/cli/evidence.d.ts +11 -0
- package/dist/tooling/cli/evidence.js +53 -0
- package/dist/tooling/cli/help.js +78 -34
- package/dist/tooling/cli/index.d.ts +1 -1
- package/dist/tooling/cli/index.js +1 -1
- package/dist/tooling/cli/installation/index.d.ts +2 -0
- package/dist/tooling/cli/installation/index.js +1 -0
- package/dist/tooling/cli/installation/observation.d.ts +10 -0
- package/dist/tooling/cli/installation/observation.js +44 -0
- package/dist/tooling/cli/installation/reconcile.d.ts +32 -0
- package/dist/tooling/cli/installation/reconcile.js +122 -0
- package/dist/tooling/cli/orchestrate.js +84 -44
- package/dist/tooling/cli/project.d.ts +17 -0
- package/dist/tooling/cli/project.js +18 -0
- package/dist/tooling/cli/test.d.ts +12 -0
- package/dist/tooling/cli/test.js +47 -0
- package/dist/tooling/cli/upgrade/index.d.ts +6 -0
- package/dist/tooling/cli/upgrade/index.js +115 -0
- package/dist/tooling/cli/upgrade/manifest.d.ts +18 -0
- package/dist/tooling/cli/upgrade/manifest.js +77 -0
- package/dist/tooling/cli/upgrade/process.d.ts +2 -0
- package/dist/tooling/cli/upgrade/process.js +57 -0
- package/dist/tooling/linter/adapters/typescript/imports.js +2 -0
- package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -0
- package/dist/tooling/linter/adapters/typescript/index.js +1 -0
- package/dist/tooling/linter/adapters/typescript/query-collection-type.js +4 -51
- package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +9 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.js +55 -0
- package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +28 -0
- package/dist/tooling/linter/implementations/source/callable-requirements.js +234 -0
- package/dist/tooling/linter/implementations/source/dependencies.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/dependencies.js +219 -0
- package/dist/tooling/linter/implementations/source/global.js +52 -2
- package/dist/tooling/linter/implementations/source/mutations.js +520 -38
- package/dist/tooling/linter/implementations/source/providers.js +19 -164
- package/dist/tooling/linter/implementations/source/schema.js +454 -2
- package/dist/tooling/linter/implementations/source/verifiers.js +2 -0
- package/dist/tooling/linter/implementations/source/workflows.js +8 -35
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/dist/tooling/linter/requirements/registry.js +9 -3
- package/dist/tooling/packaging/declarations/normalize.js +3 -38
- package/dist/tooling/packaging/declarations/paths.d.ts +5 -0
- package/dist/tooling/packaging/declarations/paths.js +38 -0
- package/dist/tooling/packaging/finalize.d.ts +13 -0
- package/dist/tooling/packaging/finalize.js +62 -0
- package/dist/tooling/packaging/index.d.ts +2 -0
- package/dist/tooling/packaging/index.js +1 -0
- package/dist/tooling/packaging/javascript.d.ts +3 -0
- package/dist/tooling/packaging/javascript.js +45 -2
- package/dist/tooling/packaging/packaging.d.ts +2 -5
- package/dist/tooling/packaging/packaging.js +6 -30
- package/package.json +42 -12
- package/CHANGELOG.md +0 -1506
- package/dist/deployment/adapter/development/context.d.ts +0 -18
- package/dist/deployment/adapter/development/index.d.ts +0 -4
- package/dist/deployment/adapter/development/input.d.ts +0 -6
- package/dist/deployment/adapter/development/placement.d.ts +0 -15
- package/dist/deployment/adapter/development/session.d.ts +0 -9
- /package/dist/{deployment/adapter/development → application/dependency}/index.js +0 -0
- /package/dist/{deployment/adapter/development/context.js → application/dependency/invoker.js} +0 -0
- /package/dist/{deployment/adapter/development/input.js → application/integration/authentication.js} +0 -0
- /package/dist/{deployment/adapter/development/placement.js → application/integration/authentication.typecheck.d.ts} +0 -0
- /package/dist/{deployment/adapter/development/session.js → application/runtime/declarations.js} +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile, unlink } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const LOCK_VERSION = 1;
|
|
5
|
+
export class DevelopmentLeaseError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
name = 'DevelopmentLeaseError';
|
|
8
|
+
constructor(code, message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** One fail-closed filesystem lease engine for every local development coordinate. */
|
|
14
|
+
export async function acquireDevelopmentLease(input) {
|
|
15
|
+
const path = join(input.directory, 'session.lock');
|
|
16
|
+
const recovery = `${path}.reclaim`;
|
|
17
|
+
const sessionId = input.sessionId ?? randomUUID();
|
|
18
|
+
const alive = input.processAlive ?? processAlive;
|
|
19
|
+
const owner = Object.freeze({
|
|
20
|
+
version: LOCK_VERSION,
|
|
21
|
+
pid: input.pid ?? process.pid,
|
|
22
|
+
sessionId,
|
|
23
|
+
coordinate: input.coordinate,
|
|
24
|
+
acquiredAt: new Date().toISOString(),
|
|
25
|
+
});
|
|
26
|
+
await mkdir(input.directory, { recursive: true, mode: 0o700 });
|
|
27
|
+
try {
|
|
28
|
+
await create(path, owner);
|
|
29
|
+
return handle(path, input.coordinate, owner, false);
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
if (errorCode(cause) !== 'EEXIST')
|
|
33
|
+
throw cause;
|
|
34
|
+
}
|
|
35
|
+
let recoveryHandle;
|
|
36
|
+
try {
|
|
37
|
+
recoveryHandle = await open(recovery, 'wx', 0o600);
|
|
38
|
+
await recoveryHandle.writeFile(`${sessionId}\n`, 'utf8');
|
|
39
|
+
await recoveryHandle.sync();
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
if (errorCode(cause) === 'EEXIST')
|
|
43
|
+
throw locked(input.coordinate);
|
|
44
|
+
throw cause;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await recoveryHandle?.close();
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const observed = await readOwner(path, input.coordinate);
|
|
51
|
+
if (observed === undefined || alive(observed.pid))
|
|
52
|
+
throw locked(input.coordinate, observed);
|
|
53
|
+
const current = await readOwner(path, input.coordinate);
|
|
54
|
+
if (!sameOwner(current, observed) || alive(observed.pid)) {
|
|
55
|
+
throw locked(input.coordinate, current);
|
|
56
|
+
}
|
|
57
|
+
await unlink(path);
|
|
58
|
+
try {
|
|
59
|
+
await create(path, owner);
|
|
60
|
+
}
|
|
61
|
+
catch (cause) {
|
|
62
|
+
if (errorCode(cause) === 'EEXIST')
|
|
63
|
+
throw locked(input.coordinate);
|
|
64
|
+
throw cause;
|
|
65
|
+
}
|
|
66
|
+
return handle(path, input.coordinate, owner, true);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await unlink(recovery).catch((cause) => {
|
|
70
|
+
if (errorCode(cause) !== 'ENOENT')
|
|
71
|
+
throw cause;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function handle(path, coordinate, owner, recovered) {
|
|
76
|
+
let released = false;
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
path,
|
|
79
|
+
recovered,
|
|
80
|
+
async release() {
|
|
81
|
+
if (released)
|
|
82
|
+
return;
|
|
83
|
+
const current = await readOwner(path, coordinate);
|
|
84
|
+
if (!sameOwner(current, owner)) {
|
|
85
|
+
throw new DevelopmentLeaseError('DEVELOPMENT_LEASE_LOST', `Development lease ownership changed for ${coordinate}; refusing to remove it.`);
|
|
86
|
+
}
|
|
87
|
+
await unlink(path);
|
|
88
|
+
released = true;
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async function create(path, owner) {
|
|
93
|
+
const file = await open(path, 'wx', 0o600);
|
|
94
|
+
try {
|
|
95
|
+
await file.writeFile(`${JSON.stringify(owner)}\n`, 'utf8');
|
|
96
|
+
await file.sync();
|
|
97
|
+
}
|
|
98
|
+
catch (cause) {
|
|
99
|
+
await file.close();
|
|
100
|
+
await unlink(path).catch(() => undefined);
|
|
101
|
+
throw cause;
|
|
102
|
+
}
|
|
103
|
+
await file.close();
|
|
104
|
+
}
|
|
105
|
+
async function readOwner(path, coordinate) {
|
|
106
|
+
let text;
|
|
107
|
+
try {
|
|
108
|
+
text = await readFile(path, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch (cause) {
|
|
111
|
+
if (errorCode(cause) === 'ENOENT')
|
|
112
|
+
return undefined;
|
|
113
|
+
throw cause;
|
|
114
|
+
}
|
|
115
|
+
let value;
|
|
116
|
+
try {
|
|
117
|
+
value = JSON.parse(text);
|
|
118
|
+
}
|
|
119
|
+
catch (cause) {
|
|
120
|
+
throw locked(coordinate, undefined, cause);
|
|
121
|
+
}
|
|
122
|
+
if (!isOwner(value) || value.coordinate !== coordinate)
|
|
123
|
+
throw locked(coordinate);
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
function isOwner(input) {
|
|
127
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
128
|
+
return false;
|
|
129
|
+
const owner = input;
|
|
130
|
+
return (Reflect.ownKeys(owner).every((key) => ['version', 'pid', 'sessionId', 'coordinate', 'acquiredAt'].includes(String(key))) &&
|
|
131
|
+
owner.version === LOCK_VERSION &&
|
|
132
|
+
Number.isSafeInteger(owner.pid) &&
|
|
133
|
+
(owner.pid ?? 0) > 0 &&
|
|
134
|
+
typeof owner.sessionId === 'string' &&
|
|
135
|
+
owner.sessionId.length > 0 &&
|
|
136
|
+
typeof owner.coordinate === 'string' &&
|
|
137
|
+
owner.coordinate.length > 0 &&
|
|
138
|
+
typeof owner.acquiredAt === 'string');
|
|
139
|
+
}
|
|
140
|
+
function sameOwner(left, right) {
|
|
141
|
+
return (left?.version === right.version &&
|
|
142
|
+
left.pid === right.pid &&
|
|
143
|
+
left.sessionId === right.sessionId &&
|
|
144
|
+
left.coordinate === right.coordinate);
|
|
145
|
+
}
|
|
146
|
+
function locked(coordinate, owner, cause) {
|
|
147
|
+
const detail = owner === undefined ? '' : ` by PID ${owner.pid} (session ${owner.sessionId})`;
|
|
148
|
+
return new DevelopmentLeaseError('DEVELOPMENT_LEASE_LOCKED', `Development coordinate ${coordinate} is already locked${detail}.`, cause === undefined ? undefined : { cause });
|
|
149
|
+
}
|
|
150
|
+
function processAlive(pid) {
|
|
151
|
+
try {
|
|
152
|
+
process.kill(pid, 0);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
catch (cause) {
|
|
156
|
+
return errorCode(cause) !== 'ESRCH';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function errorCode(input) {
|
|
160
|
+
return input !== null && typeof input === 'object' && 'code' in input
|
|
161
|
+
? String(Reflect.get(input, 'code'))
|
|
162
|
+
: undefined;
|
|
163
|
+
}
|
|
@@ -16,6 +16,9 @@ export class DevelopmentLifecycle {
|
|
|
16
16
|
// to the runtime's immediate default termination.
|
|
17
17
|
process.on('SIGINT', this.#interrupt);
|
|
18
18
|
process.on('SIGTERM', this.#interrupt);
|
|
19
|
+
// The terminal's session leader can exit before asynchronous cleanup finishes.
|
|
20
|
+
// Its hangup must request the same stop, not terminate the remaining View owner.
|
|
21
|
+
process.on('SIGHUP', this.#interrupt);
|
|
19
22
|
}
|
|
20
23
|
get signal() {
|
|
21
24
|
return this.#controller.signal;
|
|
@@ -34,6 +37,7 @@ export class DevelopmentLifecycle {
|
|
|
34
37
|
this.#disposed = true;
|
|
35
38
|
process.off('SIGINT', this.#interrupt);
|
|
36
39
|
process.off('SIGTERM', this.#interrupt);
|
|
40
|
+
process.off('SIGHUP', this.#interrupt);
|
|
37
41
|
}
|
|
38
42
|
#interrupt = () => this.stop();
|
|
39
43
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ProjectImportResolver {
|
|
2
|
+
resolveSync(specifier: string, importer: string): string;
|
|
3
|
+
}
|
|
4
|
+
interface ProjectImportResolution {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly external?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ProjectImportBuilder {
|
|
9
|
+
onResolve(options: {
|
|
10
|
+
readonly filter: RegExp;
|
|
11
|
+
}, resolve: (input: {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly importer: string;
|
|
14
|
+
}) => ProjectImportResolution | undefined): void;
|
|
15
|
+
}
|
|
16
|
+
export interface ProjectImportPlugin {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
setup(builder: ProjectImportBuilder): void;
|
|
19
|
+
}
|
|
20
|
+
/** Bundle project-local bare imports while leaving actual dependencies external. */
|
|
21
|
+
export declare function projectImportPlugin(projectDir: string, resolver: ProjectImportResolver): ProjectImportPlugin;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
/** Bundle project-local bare imports while leaving actual dependencies external. */
|
|
4
|
+
export function projectImportPlugin(projectDir, resolver) {
|
|
5
|
+
return Object.freeze({
|
|
6
|
+
name: 'astrale-project-imports',
|
|
7
|
+
setup(builder) {
|
|
8
|
+
builder.onResolve({ filter: /^[^./]/u }, (input) => {
|
|
9
|
+
let path;
|
|
10
|
+
try {
|
|
11
|
+
path = resolver.resolveSync(input.path, input.importer);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return Object.freeze({ path: input.path, external: true });
|
|
15
|
+
}
|
|
16
|
+
return projectLocal(projectDir, path)
|
|
17
|
+
? Object.freeze({ path })
|
|
18
|
+
: Object.freeze({ path: input.path, external: true });
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function projectLocal(projectDir, path) {
|
|
24
|
+
if (!isAbsolute(path))
|
|
25
|
+
return false;
|
|
26
|
+
const local = relative(canonical(projectDir), canonical(path));
|
|
27
|
+
return (local !== '..' &&
|
|
28
|
+
!local.startsWith(`..${sep}`) &&
|
|
29
|
+
!isAbsolute(local) &&
|
|
30
|
+
!local.split(sep).includes('node_modules'));
|
|
31
|
+
}
|
|
32
|
+
function canonical(path) {
|
|
33
|
+
try {
|
|
34
|
+
return realpathSync.native(path);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return resolve(path);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import type { DevelopmentLease } from './lease.js';
|
|
1
2
|
export declare class ProjectLockError extends Error {
|
|
2
3
|
readonly code: 'INVALID_ENVIRONMENT' | 'PROJECT_ENVIRONMENT_LOCKED';
|
|
3
4
|
readonly name = "ProjectLockError";
|
|
4
5
|
constructor(code: 'INVALID_ENVIRONMENT' | 'PROJECT_ENVIRONMENT_LOCKED', message: string, options?: ErrorOptions);
|
|
5
6
|
}
|
|
6
|
-
export interface ProjectLock {
|
|
7
|
-
readonly path: string;
|
|
8
|
-
readonly recovered: boolean;
|
|
9
|
-
release(): Promise<void>;
|
|
7
|
+
export interface ProjectLock extends DevelopmentLease {
|
|
10
8
|
}
|
|
9
|
+
/** Exclude concurrent writers of one checkout's generated Environment state. */
|
|
11
10
|
export declare function acquireProjectLock(input: {
|
|
12
11
|
readonly projectRoot: string;
|
|
13
12
|
readonly environment: string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
const LOCK_VERSION = 1;
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { acquireDevelopmentLease, DevelopmentLeaseError } from './lease.js';
|
|
5
4
|
export class ProjectLockError extends Error {
|
|
6
5
|
code;
|
|
7
6
|
name = 'ProjectLockError';
|
|
@@ -10,101 +9,27 @@ export class ProjectLockError extends Error {
|
|
|
10
9
|
this.code = code;
|
|
11
10
|
}
|
|
12
11
|
}
|
|
12
|
+
/** Exclude concurrent writers of one checkout's generated Environment state. */
|
|
13
13
|
export async function acquireProjectLock(input) {
|
|
14
14
|
const root = await realpath(input.projectRoot);
|
|
15
15
|
const environment = admitEnvironment(input.environment);
|
|
16
16
|
const coordinate = `${root}#${environment}`;
|
|
17
|
-
const directory = join(root, '.astrale', 'development', environment);
|
|
18
|
-
const path = join(directory, 'session.lock');
|
|
19
|
-
const recovery = `${path}.reclaim`;
|
|
20
|
-
const sessionId = input.sessionId ?? randomUUID();
|
|
21
|
-
const alive = input.processAlive ?? processAlive;
|
|
22
|
-
await mkdir(directory, { recursive: true });
|
|
23
|
-
if (await exists(recovery))
|
|
24
|
-
throw locked(coordinate);
|
|
25
|
-
const owner = Object.freeze({
|
|
26
|
-
version: LOCK_VERSION,
|
|
27
|
-
pid: input.pid ?? process.pid,
|
|
28
|
-
sessionId,
|
|
29
|
-
projectRoot: root,
|
|
30
|
-
environment,
|
|
31
|
-
acquiredAt: new Date().toISOString(),
|
|
32
|
-
});
|
|
33
17
|
try {
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const observed = await readOwner(path, coordinate);
|
|
42
|
-
if (observed === undefined ||
|
|
43
|
-
alive(observed.pid) ||
|
|
44
|
-
observed.projectRoot !== root ||
|
|
45
|
-
observed.environment !== environment) {
|
|
46
|
-
throw locked(coordinate, observed);
|
|
47
|
-
}
|
|
48
|
-
let recoveryHandle;
|
|
49
|
-
try {
|
|
50
|
-
recoveryHandle = await open(recovery, 'wx', 0o600);
|
|
51
|
-
await recoveryHandle.writeFile(`${sessionId}\n`, 'utf8');
|
|
52
|
-
await recoveryHandle.sync();
|
|
18
|
+
return await acquireDevelopmentLease({
|
|
19
|
+
directory: join(root, '.astrale', 'development', environment),
|
|
20
|
+
coordinate,
|
|
21
|
+
...(input.pid === undefined ? {} : { pid: input.pid }),
|
|
22
|
+
...(input.sessionId === undefined ? {} : { sessionId: input.sessionId }),
|
|
23
|
+
...(input.processAlive === undefined ? {} : { processAlive: input.processAlive }),
|
|
24
|
+
});
|
|
53
25
|
}
|
|
54
26
|
catch (cause) {
|
|
55
|
-
if (
|
|
56
|
-
throw
|
|
57
|
-
throw cause;
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
await recoveryHandle?.close();
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const current = await readOwner(path, coordinate);
|
|
64
|
-
if (current === undefined ||
|
|
65
|
-
current.sessionId !== observed.sessionId ||
|
|
66
|
-
current.pid !== observed.pid ||
|
|
67
|
-
alive(current.pid)) {
|
|
68
|
-
throw locked(coordinate, current);
|
|
69
|
-
}
|
|
70
|
-
await unlink(path);
|
|
71
|
-
try {
|
|
72
|
-
await create(path, owner);
|
|
73
|
-
}
|
|
74
|
-
catch (cause) {
|
|
75
|
-
if (errorCode(cause) === 'EEXIST')
|
|
76
|
-
throw locked(coordinate, await readOwner(path, coordinate));
|
|
77
|
-
throw cause;
|
|
27
|
+
if (cause instanceof DevelopmentLeaseError) {
|
|
28
|
+
throw new ProjectLockError('PROJECT_ENVIRONMENT_LOCKED', cause.message, { cause });
|
|
78
29
|
}
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
finally {
|
|
82
|
-
await unlink(recovery).catch((cause) => {
|
|
83
|
-
if (errorCode(cause) !== 'ENOENT')
|
|
84
|
-
throw cause;
|
|
85
|
-
});
|
|
30
|
+
throw cause;
|
|
86
31
|
}
|
|
87
32
|
}
|
|
88
|
-
function handle(path, coordinate, sessionId, recovered) {
|
|
89
|
-
return Object.freeze({
|
|
90
|
-
path,
|
|
91
|
-
recovered,
|
|
92
|
-
async release() {
|
|
93
|
-
const current = await readOwner(path, coordinate);
|
|
94
|
-
if (current !== undefined && current.sessionId === sessionId) {
|
|
95
|
-
await unlink(path);
|
|
96
|
-
await removeEmpty(dirname(path));
|
|
97
|
-
await removeEmpty(dirname(dirname(path)));
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
async function removeEmpty(path) {
|
|
103
|
-
await rmdir(path).catch((cause) => {
|
|
104
|
-
if (!['ENOENT', 'ENOTEMPTY'].includes(errorCode(cause) ?? ''))
|
|
105
|
-
throw cause;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
33
|
function admitEnvironment(input) {
|
|
109
34
|
if (typeof input !== 'string' ||
|
|
110
35
|
input === '.' ||
|
|
@@ -114,80 +39,3 @@ function admitEnvironment(input) {
|
|
|
114
39
|
}
|
|
115
40
|
return input;
|
|
116
41
|
}
|
|
117
|
-
function processAlive(pid) {
|
|
118
|
-
try {
|
|
119
|
-
process.kill(pid, 0);
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
catch (cause) {
|
|
123
|
-
return errorCode(cause) !== 'ESRCH';
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function exists(path) {
|
|
127
|
-
try {
|
|
128
|
-
await stat(path);
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
catch (cause) {
|
|
132
|
-
if (errorCode(cause) === 'ENOENT')
|
|
133
|
-
return false;
|
|
134
|
-
throw cause;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
async function create(path, owner) {
|
|
138
|
-
const file = await open(path, 'wx', 0o600);
|
|
139
|
-
try {
|
|
140
|
-
await file.writeFile(`${JSON.stringify(owner)}\n`, 'utf8');
|
|
141
|
-
await file.sync();
|
|
142
|
-
}
|
|
143
|
-
catch (cause) {
|
|
144
|
-
await file.close();
|
|
145
|
-
await unlink(path).catch(() => undefined);
|
|
146
|
-
throw cause;
|
|
147
|
-
}
|
|
148
|
-
await file.close();
|
|
149
|
-
}
|
|
150
|
-
async function readOwner(path, coordinate) {
|
|
151
|
-
let text;
|
|
152
|
-
try {
|
|
153
|
-
text = await readFile(path, 'utf8');
|
|
154
|
-
}
|
|
155
|
-
catch (cause) {
|
|
156
|
-
if (errorCode(cause) === 'ENOENT')
|
|
157
|
-
return undefined;
|
|
158
|
-
throw cause;
|
|
159
|
-
}
|
|
160
|
-
let value;
|
|
161
|
-
try {
|
|
162
|
-
value = JSON.parse(text);
|
|
163
|
-
}
|
|
164
|
-
catch (cause) {
|
|
165
|
-
throw new ProjectLockError('PROJECT_ENVIRONMENT_LOCKED', `Development lock for ${coordinate} is unreadable; refusing unsafe recovery.`, { cause });
|
|
166
|
-
}
|
|
167
|
-
if (!isOwner(value)) {
|
|
168
|
-
throw new ProjectLockError('PROJECT_ENVIRONMENT_LOCKED', `Development lock for ${coordinate} has invalid owner evidence; refusing unsafe recovery.`);
|
|
169
|
-
}
|
|
170
|
-
return value;
|
|
171
|
-
}
|
|
172
|
-
function isOwner(input) {
|
|
173
|
-
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
174
|
-
return false;
|
|
175
|
-
const owner = input;
|
|
176
|
-
return (owner.version === LOCK_VERSION &&
|
|
177
|
-
Number.isInteger(owner.pid) &&
|
|
178
|
-
(owner.pid ?? 0) > 0 &&
|
|
179
|
-
typeof owner.sessionId === 'string' &&
|
|
180
|
-
owner.sessionId.length > 0 &&
|
|
181
|
-
typeof owner.projectRoot === 'string' &&
|
|
182
|
-
typeof owner.environment === 'string' &&
|
|
183
|
-
typeof owner.acquiredAt === 'string');
|
|
184
|
-
}
|
|
185
|
-
function locked(coordinate, owner) {
|
|
186
|
-
const detail = owner === undefined ? '' : ` by PID ${owner.pid} (session ${owner.sessionId})`;
|
|
187
|
-
return new ProjectLockError('PROJECT_ENVIRONMENT_LOCKED', `Development session ${coordinate} is already locked${detail}.`);
|
|
188
|
-
}
|
|
189
|
-
function errorCode(input) {
|
|
190
|
-
return input !== null && typeof input === 'object' && 'code' in input
|
|
191
|
-
? String(Reflect.get(input, 'code'))
|
|
192
|
-
: undefined;
|
|
193
|
-
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InstallationEvidence } from '../installation/index.js';
|
|
2
|
+
import type { DevelopmentSessionReleaseEvidenceV1 } from './session-file.js';
|
|
3
|
+
/** Project installed evidence projected into the stable development-session representation. */
|
|
4
|
+
export declare function developmentReleaseEvidence(installation: InstallationEvidence): DevelopmentSessionReleaseEvidenceV1;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Project installed evidence projected into the stable development-session representation. */
|
|
2
|
+
export function developmentReleaseEvidence(installation) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
origin: installation.origin,
|
|
5
|
+
revision: installation.revision,
|
|
6
|
+
issuer: installation.issuer,
|
|
7
|
+
etag: installation.etag,
|
|
8
|
+
generation: installation.generation,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Adapter, ReadyDeployResult } from '../../../deployment/adapter/index.js';
|
|
2
|
+
import type { InstallationEvidence } from '../installation/index.js';
|
|
3
|
+
export declare function reportDevelopmentDeployment(deployed: ReadyDeployResult, installation: InstallationEvidence | undefined, adapter: Adapter<object>, input: {
|
|
4
|
+
readonly environment: string;
|
|
5
|
+
readonly deployOnly: boolean;
|
|
6
|
+
}): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { info } from '../log.js';
|
|
2
|
+
export function reportDevelopmentDeployment(deployed, installation, adapter, input) {
|
|
3
|
+
info(`Environment: ${input.environment}`);
|
|
4
|
+
info(`Adapter: ${adapter.name}@${adapter.version}`);
|
|
5
|
+
info(`Deployment: ${deployed.progress.provider.evidence.deployment}`);
|
|
6
|
+
if (deployed.progress.provider.evidence.preflight !== undefined) {
|
|
7
|
+
info(`Preflight: ${deployed.progress.provider.evidence.preflight}; public readiness verified`);
|
|
8
|
+
}
|
|
9
|
+
info(`Public: ${deployed.release.addressing.publicUrl}`);
|
|
10
|
+
info(`Publication: ${deployed.release.publication.schema.revision} · ${deployed.release.publication.etag}`);
|
|
11
|
+
info(installation === undefined
|
|
12
|
+
? `Installation: skipped${input.deployOnly ? ' (--deploy-only)' : ''}`
|
|
13
|
+
: `Installation: ${installation.instance} · ${installation.status}`);
|
|
14
|
+
}
|
|
@@ -10,6 +10,7 @@ export function watchSources(input) {
|
|
|
10
10
|
const config = resolve(input.configPath);
|
|
11
11
|
let configFingerprint = fingerprint(config);
|
|
12
12
|
let sourceFingerprints = fingerprints(sources);
|
|
13
|
+
const directoryWatchers = new Map();
|
|
13
14
|
const watcher = watch(input.projectDir, { recursive: true }, () => {
|
|
14
15
|
if (closed)
|
|
15
16
|
return;
|
|
@@ -36,11 +37,39 @@ export function watchSources(input) {
|
|
|
36
37
|
}
|
|
37
38
|
if (!changed)
|
|
38
39
|
return;
|
|
40
|
+
schedule();
|
|
41
|
+
});
|
|
42
|
+
watcher.on('error', input.onError);
|
|
43
|
+
syncDirectoryWatchers();
|
|
44
|
+
function schedule() {
|
|
39
45
|
if (timer !== undefined)
|
|
40
46
|
clearTimeout(timer);
|
|
41
47
|
timer = setTimeout(flush, input.debounceMs ?? 80);
|
|
42
|
-
}
|
|
43
|
-
|
|
48
|
+
}
|
|
49
|
+
function syncDirectoryWatchers() {
|
|
50
|
+
const directories = new Set([...sourceFingerprints]
|
|
51
|
+
.filter(([, value]) => value.startsWith('directory:'))
|
|
52
|
+
.map(([path]) => path));
|
|
53
|
+
for (const [path, owned] of directoryWatchers) {
|
|
54
|
+
if (directories.has(path))
|
|
55
|
+
continue;
|
|
56
|
+
owned.off('error', input.onError);
|
|
57
|
+
owned.close();
|
|
58
|
+
directoryWatchers.delete(path);
|
|
59
|
+
}
|
|
60
|
+
for (const path of directories) {
|
|
61
|
+
if (directoryWatchers.has(path))
|
|
62
|
+
continue;
|
|
63
|
+
const owned = watch(path, () => {
|
|
64
|
+
if (closed)
|
|
65
|
+
return;
|
|
66
|
+
dirty = true;
|
|
67
|
+
schedule();
|
|
68
|
+
});
|
|
69
|
+
owned.on('error', input.onError);
|
|
70
|
+
directoryWatchers.set(path, owned);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
44
73
|
function flush() {
|
|
45
74
|
timer = undefined;
|
|
46
75
|
if (closed)
|
|
@@ -54,8 +83,22 @@ export function watchSources(input) {
|
|
|
54
83
|
}
|
|
55
84
|
return Object.freeze({
|
|
56
85
|
update(next) {
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
const nextSources = canonicalSources(next);
|
|
87
|
+
const nextFingerprints = fingerprints(nextSources);
|
|
88
|
+
// A completed build does not prove it consumed the current file contents.
|
|
89
|
+
// Retain edits that raced its commit, even if the OS event is still queued.
|
|
90
|
+
let changed = false;
|
|
91
|
+
for (const [path, value] of nextFingerprints) {
|
|
92
|
+
if (sourceFingerprints.has(path) && sourceFingerprints.get(path) !== value) {
|
|
93
|
+
dirty = true;
|
|
94
|
+
changed = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
sources = nextSources;
|
|
98
|
+
sourceFingerprints = nextFingerprints;
|
|
99
|
+
syncDirectoryWatchers();
|
|
100
|
+
if (changed)
|
|
101
|
+
schedule();
|
|
59
102
|
},
|
|
60
103
|
async stop() {
|
|
61
104
|
if (closed)
|
|
@@ -65,6 +108,11 @@ export function watchSources(input) {
|
|
|
65
108
|
clearTimeout(timer);
|
|
66
109
|
watcher.off('error', input.onError);
|
|
67
110
|
watcher.close();
|
|
111
|
+
for (const owned of directoryWatchers.values()) {
|
|
112
|
+
owned.off('error', input.onError);
|
|
113
|
+
owned.close();
|
|
114
|
+
}
|
|
115
|
+
directoryWatchers.clear();
|
|
68
116
|
},
|
|
69
117
|
});
|
|
70
118
|
}
|
|
@@ -77,7 +125,7 @@ function fingerprints(paths) {
|
|
|
77
125
|
function fingerprint(path) {
|
|
78
126
|
try {
|
|
79
127
|
const value = statSync(path, { bigint: true });
|
|
80
|
-
return `${value.size}:${value.mtimeNs}:${value.ctimeNs}`;
|
|
128
|
+
return `${value.isDirectory() ? 'directory' : 'file'}:${value.size}:${value.mtimeNs}:${value.ctimeNs}`;
|
|
81
129
|
}
|
|
82
130
|
catch (cause) {
|
|
83
131
|
if (errorCode(cause) === 'ENOENT')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
/** Own the canonical project-local files and directories that invalidate a candidate. */
|
|
4
|
+
export class DevelopmentSources {
|
|
5
|
+
#declaredProjectDir;
|
|
6
|
+
#projectDir;
|
|
7
|
+
#sources = new Set();
|
|
8
|
+
constructor(projectDir) {
|
|
9
|
+
this.#declaredProjectDir = resolve(projectDir);
|
|
10
|
+
try {
|
|
11
|
+
this.#projectDir = realpathSync.native(this.#declaredProjectDir);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
this.#projectDir = this.#declaredProjectDir;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
record = (path) => {
|
|
18
|
+
if (typeof path !== 'string' || path.length === 0 || path.includes('\0')) {
|
|
19
|
+
throw new TypeError('An adapter reported an invalid preparation source path.');
|
|
20
|
+
}
|
|
21
|
+
const declared = isAbsolute(path) ? resolve(path) : resolve(this.#declaredProjectDir, path);
|
|
22
|
+
let absolute;
|
|
23
|
+
try {
|
|
24
|
+
absolute = realpathSync.native(declared);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
if (errorCode(cause) !== 'ENOENT')
|
|
28
|
+
throw cause;
|
|
29
|
+
absolute = declared.startsWith(`${this.#declaredProjectDir}${sep}`)
|
|
30
|
+
? resolve(this.#projectDir, relative(this.#declaredProjectDir, declared))
|
|
31
|
+
: declared;
|
|
32
|
+
}
|
|
33
|
+
if (!absolute.startsWith(`${this.#projectDir}${sep}`))
|
|
34
|
+
return;
|
|
35
|
+
const topLevel = relative(this.#projectDir, absolute).split(sep)[0];
|
|
36
|
+
if (topLevel === '.astrale' || topLevel === '.git' || topLevel === 'node_modules')
|
|
37
|
+
return;
|
|
38
|
+
this.#sources.add(absolute);
|
|
39
|
+
};
|
|
40
|
+
snapshot() {
|
|
41
|
+
return new Set(this.#sources);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function errorCode(input) {
|
|
45
|
+
return input !== null && typeof input === 'object' && 'code' in input
|
|
46
|
+
? String(Reflect.get(input, 'code'))
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DevelopmentLease } from './lease.js';
|
|
2
|
+
export declare class DevelopmentTargetLockError extends Error {
|
|
3
|
+
readonly code: 'DEVELOPMENT_TARGET_LOCKED' | 'DEVELOPMENT_TARGET_LOCK_LOST';
|
|
4
|
+
readonly name = "DevelopmentTargetLockError";
|
|
5
|
+
constructor(code: 'DEVELOPMENT_TARGET_LOCKED' | 'DEVELOPMENT_TARGET_LOCK_LOST', message: string, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
/** Exclude local sessions targeting one installed Domain across separate checkouts. */
|
|
8
|
+
export declare function acquireDevelopmentTargetLock(input: {
|
|
9
|
+
readonly issuer: string;
|
|
10
|
+
readonly origin: string;
|
|
11
|
+
}, dependencies?: {
|
|
12
|
+
readonly root?: string;
|
|
13
|
+
readonly pid?: number;
|
|
14
|
+
readonly sessionId?: string;
|
|
15
|
+
readonly processAlive?: (pid: number) => boolean;
|
|
16
|
+
}): Promise<DevelopmentLease>;
|