@principles/install-layout 0.1.1 → 0.2.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/dist/index.d.ts +15 -0
- package/dist/index.js +35 -1
- package/dist/index.test.js +36 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export interface InstallManifest {
|
|
|
7
7
|
layoutVersion: typeof INSTALL_LAYOUT_VERSION;
|
|
8
8
|
mode: InstallLayoutMode;
|
|
9
9
|
hosts: InstallHost[];
|
|
10
|
+
/**
|
|
11
|
+
* Canonical absolute paths of Workspaces the install serves (PRI-624 Slice C).
|
|
12
|
+
* Optional so pre-PRI-624 manifests stay valid; always normalized to
|
|
13
|
+
* resolved absolute paths and deduped. The installer merges the install
|
|
14
|
+
* Workspace in; the Companion worker discovers its per-Workspace workers
|
|
15
|
+
* from this list (SPEC §13 "canonical install manifest").
|
|
16
|
+
*/
|
|
17
|
+
workspaces?: readonly string[];
|
|
10
18
|
}
|
|
11
19
|
export interface InstallLayoutPaths {
|
|
12
20
|
homeDir: string;
|
|
@@ -23,6 +31,11 @@ export interface InstallLayoutPaths {
|
|
|
23
31
|
codexDir: string;
|
|
24
32
|
openClawDir: string;
|
|
25
33
|
openClawExtensionDir: string;
|
|
34
|
+
/**
|
|
35
|
+
* PRI-672: the ReleaseManager authority component (payload dir
|
|
36
|
+
* release-manager/, npm name create-principles-disciple).
|
|
37
|
+
*/
|
|
38
|
+
releaseManagerDir: string;
|
|
26
39
|
}
|
|
27
40
|
export interface LayoutResolution {
|
|
28
41
|
mode: InstallLayoutMode | 'missing';
|
|
@@ -37,6 +50,8 @@ export interface ResolveInstallLayoutOptions {
|
|
|
37
50
|
canonicalRuntimeExists: boolean;
|
|
38
51
|
legacyExtensionExists: boolean;
|
|
39
52
|
}
|
|
53
|
+
/** Merge one Workspace into a manifest's workspace list (installer write path). Idempotent by canonical path. */
|
|
54
|
+
export declare function mergeInstallManifestWorkspaces(current: InstallManifest | undefined, workspaceDir: string): string[];
|
|
40
55
|
export declare function parseInstallManifest(value: unknown): {
|
|
41
56
|
manifest?: InstallManifest;
|
|
42
57
|
error?: string;
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,36 @@ function isRecord(value) {
|
|
|
8
8
|
function isHost(value) {
|
|
9
9
|
return typeof value === 'string' && INSTALL_HOSTS.includes(value);
|
|
10
10
|
}
|
|
11
|
+
/** Workspace entries must be non-empty absolute paths — the Companion spawns per-Workspace workers keyed by canonical path. */
|
|
12
|
+
function parseWorkspaceList(value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return { workspaces: [] };
|
|
15
|
+
if (!Array.isArray(value))
|
|
16
|
+
return { error: 'install_manifest_malformed: workspaces must be non-empty absolute paths' };
|
|
17
|
+
const resolved = [];
|
|
18
|
+
for (const entry of value) {
|
|
19
|
+
if (typeof entry !== 'string' || entry.length === 0 || !path.isAbsolute(entry)) {
|
|
20
|
+
return { error: 'install_manifest_malformed: workspaces must be non-empty absolute paths' };
|
|
21
|
+
}
|
|
22
|
+
const canonical = path.resolve(entry);
|
|
23
|
+
if (!resolved.includes(canonical))
|
|
24
|
+
resolved.push(canonical);
|
|
25
|
+
}
|
|
26
|
+
return { workspaces: resolved };
|
|
27
|
+
}
|
|
28
|
+
/** Merge one Workspace into a manifest's workspace list (installer write path). Idempotent by canonical path. */
|
|
29
|
+
export function mergeInstallManifestWorkspaces(current, workspaceDir) {
|
|
30
|
+
const canonical = path.resolve(workspaceDir);
|
|
31
|
+
const resolved = [];
|
|
32
|
+
for (const entry of current?.workspaces ?? []) {
|
|
33
|
+
const normalized = path.resolve(entry);
|
|
34
|
+
if (!resolved.includes(normalized))
|
|
35
|
+
resolved.push(normalized);
|
|
36
|
+
}
|
|
37
|
+
if (!resolved.includes(canonical))
|
|
38
|
+
resolved.push(canonical);
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
11
41
|
export function parseInstallManifest(value) {
|
|
12
42
|
if (!isRecord(value))
|
|
13
43
|
return { error: 'install_manifest_malformed: expected an object' };
|
|
@@ -21,7 +51,10 @@ export function parseInstallManifest(value) {
|
|
|
21
51
|
return { error: 'install_manifest_malformed: hosts must contain codex and/or openclaw' };
|
|
22
52
|
}
|
|
23
53
|
const hosts = [...new Set(value.hosts)];
|
|
24
|
-
|
|
54
|
+
const workspaces = parseWorkspaceList(value.workspaces);
|
|
55
|
+
if (workspaces.error)
|
|
56
|
+
return { error: workspaces.error };
|
|
57
|
+
return { manifest: { layoutVersion: INSTALL_LAYOUT_VERSION, mode: value.mode, hosts, workspaces: workspaces.workspaces } };
|
|
25
58
|
}
|
|
26
59
|
export function getInstallLayoutPaths(homeDir) {
|
|
27
60
|
const home = path.resolve(homeDir);
|
|
@@ -43,6 +76,7 @@ export function getInstallLayoutPaths(homeDir) {
|
|
|
43
76
|
codexDir: path.join(pdDir, 'codex'),
|
|
44
77
|
openClawDir,
|
|
45
78
|
openClawExtensionDir: path.join(openClawDir, 'extensions', 'principles-disciple'),
|
|
79
|
+
releaseManagerDir: path.join(runtimeDir, 'release-manager'),
|
|
46
80
|
};
|
|
47
81
|
}
|
|
48
82
|
export function resolveInstallLayout(options) {
|
package/dist/index.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { getInstallLayoutPaths, getConsoleServerEntry, parseInstallManifest, resolveInstallLayout, mergeInstallManifestWorkspaces, } from './index.js';
|
|
3
4
|
describe('install layout', () => {
|
|
4
5
|
it('resolves a canonical Codex-only install without OpenClaw', () => {
|
|
5
6
|
const paths = getInstallLayoutPaths('C:/Users/alice');
|
|
@@ -31,4 +32,38 @@ describe('install layout', () => {
|
|
|
31
32
|
error: 'install_manifest_malformed: hosts must contain codex and/or openclaw',
|
|
32
33
|
});
|
|
33
34
|
});
|
|
35
|
+
it('accepts a manifest without workspaces (pre-PRI-624 manifests stay valid)', () => {
|
|
36
|
+
const { manifest, error } = parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'] });
|
|
37
|
+
expect(error).toBeUndefined();
|
|
38
|
+
expect(manifest?.workspaces).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
it('parses workspaces as canonical absolute paths, deduped in insertion order', () => {
|
|
41
|
+
const { manifest, error } = parseInstallManifest({
|
|
42
|
+
layoutVersion: 1,
|
|
43
|
+
mode: 'canonical',
|
|
44
|
+
hosts: ['codex'],
|
|
45
|
+
workspaces: ['D:/Code/ws-a', 'D:\\Code\\ws-a', 'C:/work/ws b'],
|
|
46
|
+
});
|
|
47
|
+
expect(error).toBeUndefined();
|
|
48
|
+
expect(manifest?.workspaces?.length).toBe(2);
|
|
49
|
+
expect(manifest?.workspaces?.[0]).toBe(path.resolve('D:/Code/ws-a'));
|
|
50
|
+
expect(manifest?.workspaces).toContain(path.resolve('C:/work/ws b'));
|
|
51
|
+
});
|
|
52
|
+
it('rejects malformed workspace entries loudly', () => {
|
|
53
|
+
expect(parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: [''] })).toEqual({
|
|
54
|
+
error: 'install_manifest_malformed: workspaces must be non-empty absolute paths',
|
|
55
|
+
});
|
|
56
|
+
expect(parseInstallManifest({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: ['ws-a'] })).toEqual({
|
|
57
|
+
error: 'install_manifest_malformed: workspaces must be non-empty absolute paths',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('merges a workspace into an existing manifest without duplicates', () => {
|
|
61
|
+
const merged = mergeInstallManifestWorkspaces({ layoutVersion: 1, mode: 'canonical', hosts: ['codex'], workspaces: ['D:/Code/ws-a'] }, 'D:\\Code\\ws-a');
|
|
62
|
+
expect(merged.length).toBe(1);
|
|
63
|
+
expect(merged[0]).toBe(path.resolve('D:/Code/ws-a'));
|
|
64
|
+
});
|
|
65
|
+
it('merge accepts unknown current manifests and starts a fresh list', () => {
|
|
66
|
+
const merged = mergeInstallManifestWorkspaces(undefined, 'D:/Code/ws-b');
|
|
67
|
+
expect(merged).toEqual([path.resolve('D:/Code/ws-b')]);
|
|
68
|
+
});
|
|
34
69
|
});
|