reffy-cli 1.4.0 → 1.4.2
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 +21 -2
- package/dist/cli.js +89 -20
- package/dist/gitignore.d.ts +4 -0
- package/dist/gitignore.js +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/remote.d.ts +9 -0
- package/dist/remote.js +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,8 +99,8 @@ The current remote flow is:
|
|
|
99
99
|
1. Reffy reads `project_id` and `workspace_name` from `.reffy/manifest.json`.
|
|
100
100
|
2. Reffy connects to Paseo using `PASEO_ENDPOINT` or `--endpoint`.
|
|
101
101
|
3. `reffy remote init --provision` creates a pod and actor when needed, then writes local linkage state to `.reffy/state/remote.json`.
|
|
102
|
-
4. `reffy remote push` publishes the local workspace to that linked actor.
|
|
103
|
-
5. `reffy remote status|ls|cat` inspects the linked remote workspace.
|
|
102
|
+
4. `reffy remote push` publishes the local workspace to that linked actor with `replace_missing=true` by default, so remote reflects local.
|
|
103
|
+
5. `reffy remote status|ls|cat` inspects the linked remote workspace using the saved linkage state unless you override it.
|
|
104
104
|
|
|
105
105
|
### Minimal connection requirement
|
|
106
106
|
|
|
@@ -141,6 +141,8 @@ That file contains the connection tuple Reffy uses to reach the backend:
|
|
|
141
141
|
|
|
142
142
|
This file is local runtime state. It is not part of the synced remote workspace and is intentionally excluded from `reffy remote push`.
|
|
143
143
|
|
|
144
|
+
The file can also record local sync metadata such as the last successful import timestamp. That metadata is for local diagnostics and does not become part of the remote workspace contract.
|
|
145
|
+
|
|
144
146
|
### Example flow
|
|
145
147
|
|
|
146
148
|
```bash
|
|
@@ -152,6 +154,23 @@ reffy remote ls
|
|
|
152
154
|
reffy remote cat .reffy/manifest.json
|
|
153
155
|
```
|
|
154
156
|
|
|
157
|
+
`reffy remote status` is the primary diagnostic command for this flow. It reports:
|
|
158
|
+
|
|
159
|
+
- the saved linkage being used
|
|
160
|
+
- the local manifest identity
|
|
161
|
+
- the remote workspace identity
|
|
162
|
+
- remote document counts when reachable
|
|
163
|
+
|
|
164
|
+
`reffy remote push` reports:
|
|
165
|
+
|
|
166
|
+
- local document count
|
|
167
|
+
- imported count
|
|
168
|
+
- created count
|
|
169
|
+
- updated count
|
|
170
|
+
- deleted count
|
|
171
|
+
|
|
172
|
+
That makes the default prune/import behavior auditable without dropping to direct backend API calls.
|
|
173
|
+
|
|
155
174
|
## Manifest Contract
|
|
156
175
|
|
|
157
176
|
Reffy keeps workspace metadata in `.reffy/manifest.json`. New managed manifests include both core timestamps and workspace identity:
|
package/dist/cli.js
CHANGED
|
@@ -5,11 +5,12 @@ import path from "node:path";
|
|
|
5
5
|
import { renderDiagram } from "./diagram.js";
|
|
6
6
|
import { runDoctor } from "./doctor.js";
|
|
7
7
|
import { loadDotEnvIfPresent } from "./env.js";
|
|
8
|
+
import { ensureGitignoreEntries } from "./gitignore.js";
|
|
8
9
|
import { archivePlanningChange } from "./plan-archive.js";
|
|
9
10
|
import { createPlanScaffold } from "./plan.js";
|
|
10
11
|
import { DEFAULT_PLANNING_RELATIVE_DIR, looksLikePlanningDir, resolveCanonicalPlanningPath } from "./planning-paths.js";
|
|
11
12
|
import { listPlanningChanges, showPlanningChange, validatePlanningChange } from "./plan-runtime.js";
|
|
12
|
-
import { assertRemoteIdentity, collectWorkspaceDocuments, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, requireWorkspaceIdentity, resolveRemoteConfigPath, } from "./remote.js";
|
|
13
|
+
import { assertRemoteIdentity, collectWorkspaceDocuments, describeRemoteLinkage, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, requireWorkspaceIdentity, resolveRemoteConfigPath, updateRemoteConfigMetadata, validateImportResult, } from "./remote.js";
|
|
13
14
|
import { DEFAULT_REFS_DIRNAME, detectWorkspaceState, looksLikeRefsDir } from "./refs-paths.js";
|
|
14
15
|
import { prepareCanonicalPlanningLayout } from "./planning-workspace.js";
|
|
15
16
|
import { ReferencesStore } from "./storage.js";
|
|
@@ -813,17 +814,25 @@ async function resolveRemoteContext(args) {
|
|
|
813
814
|
const store = new ReferencesStore(args.repoRoot);
|
|
814
815
|
const identity = requireWorkspaceIdentity(await store.getWorkspaceIdentity());
|
|
815
816
|
const savedConfig = await readRemoteConfig(args.repoRoot);
|
|
816
|
-
const
|
|
817
|
+
const overrideValues = {
|
|
817
818
|
endpoint: args.endpoint ?? process.env.PASEO_ENDPOINT,
|
|
818
819
|
pod_name: args.podName ?? process.env.PASEO_POD_NAME,
|
|
819
820
|
actor_id: args.actorId ?? process.env.PASEO_ACTOR_ID,
|
|
820
|
-
}
|
|
821
|
+
};
|
|
822
|
+
const mergedConfig = mergeRemoteConfig(savedConfig, overrideValues);
|
|
823
|
+
const hasOverrides = Boolean(overrideValues.endpoint || overrideValues.pod_name || overrideValues.actor_id);
|
|
824
|
+
const linkageSource = mergedConfig
|
|
825
|
+
? savedConfig && !hasOverrides
|
|
826
|
+
? "saved_config"
|
|
827
|
+
: "cli_or_env_overrides"
|
|
828
|
+
: "unconfigured";
|
|
821
829
|
return {
|
|
822
830
|
store,
|
|
823
831
|
identity,
|
|
824
832
|
configPath: resolveRemoteConfigPath(args.repoRoot),
|
|
825
833
|
savedConfig,
|
|
826
834
|
mergedConfig,
|
|
835
|
+
linkageSource,
|
|
827
836
|
};
|
|
828
837
|
}
|
|
829
838
|
async function main() {
|
|
@@ -885,7 +894,7 @@ async function main() {
|
|
|
885
894
|
const parsed = parseRemoteArgs(remoteArgs);
|
|
886
895
|
try {
|
|
887
896
|
if (subcommand === "init") {
|
|
888
|
-
const { identity } = await resolveRemoteContext(parsed);
|
|
897
|
+
const { identity, configPath } = await resolveRemoteContext(parsed);
|
|
889
898
|
const endpoint = parsed.endpoint ?? process.env.PASEO_ENDPOINT;
|
|
890
899
|
if (!endpoint) {
|
|
891
900
|
throw new Error("Remote init requires --endpoint or PASEO_ENDPOINT (optionally loaded from .env)");
|
|
@@ -897,12 +906,13 @@ async function main() {
|
|
|
897
906
|
provision: parsed.provision,
|
|
898
907
|
identity,
|
|
899
908
|
});
|
|
909
|
+
const gitignore = await ensureGitignoreEntries(parsed.repoRoot, [".reffy/state/"]);
|
|
900
910
|
const payload = {
|
|
901
911
|
status: "ok",
|
|
902
912
|
command: "remote",
|
|
903
913
|
subcommand: "init",
|
|
904
914
|
provider: result.config.provider,
|
|
905
|
-
config_path:
|
|
915
|
+
config_path: configPath,
|
|
906
916
|
endpoint: result.config.endpoint,
|
|
907
917
|
pod_name: result.config.pod_name,
|
|
908
918
|
actor_id: result.config.actor_id,
|
|
@@ -910,6 +920,9 @@ async function main() {
|
|
|
910
920
|
created_actor: result.created_actor,
|
|
911
921
|
project_id: identity.project_id,
|
|
912
922
|
workspace_name: identity.workspace_name,
|
|
923
|
+
linkage_mode: result.created_pod || result.created_actor ? "provisioned" : "existing",
|
|
924
|
+
gitignore_path: gitignore.path,
|
|
925
|
+
gitignore_added: gitignore.added,
|
|
913
926
|
};
|
|
914
927
|
if (output === "json") {
|
|
915
928
|
printResult(output, payload);
|
|
@@ -924,6 +937,10 @@ async function main() {
|
|
|
924
937
|
console.log(`Workspace name: ${payload.workspace_name}`);
|
|
925
938
|
console.log(`Created pod: ${payload.created_pod ? "yes" : "no"}`);
|
|
926
939
|
console.log(`Created actor: ${payload.created_actor ? "yes" : "no"}`);
|
|
940
|
+
console.log(`Linkage mode: ${payload.linkage_mode}`);
|
|
941
|
+
if (payload.gitignore_added.length > 0) {
|
|
942
|
+
console.log(`Updated .gitignore: ${payload.gitignore_added.join(", ")}`);
|
|
943
|
+
}
|
|
927
944
|
}
|
|
928
945
|
return 0;
|
|
929
946
|
}
|
|
@@ -933,7 +950,14 @@ async function main() {
|
|
|
933
950
|
}
|
|
934
951
|
const client = new PaseoRemoteClient(context.mergedConfig);
|
|
935
952
|
if (subcommand === "status") {
|
|
936
|
-
|
|
953
|
+
let workspace;
|
|
954
|
+
try {
|
|
955
|
+
workspace = await client.getWorkspace();
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
959
|
+
throw new Error(`Remote status failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
|
|
960
|
+
}
|
|
937
961
|
assertRemoteIdentity(workspace, context.identity);
|
|
938
962
|
const remoteIdentity = extractWorkspaceIdentity(workspace);
|
|
939
963
|
const payload = {
|
|
@@ -945,12 +969,14 @@ async function main() {
|
|
|
945
969
|
endpoint: context.mergedConfig.endpoint,
|
|
946
970
|
pod_name: context.mergedConfig.pod_name,
|
|
947
971
|
actor_id: context.mergedConfig.actor_id,
|
|
972
|
+
linkage_source: context.linkageSource,
|
|
948
973
|
local_identity: context.identity,
|
|
949
974
|
remote_identity: {
|
|
950
975
|
project_id: remoteIdentity.project_id,
|
|
951
976
|
workspace_name: remoteIdentity.workspace_name,
|
|
952
977
|
},
|
|
953
978
|
document_count: remoteIdentity.document_count,
|
|
979
|
+
last_imported_at: context.savedConfig?.last_imported_at ?? null,
|
|
954
980
|
workspace,
|
|
955
981
|
};
|
|
956
982
|
if (output === "json") {
|
|
@@ -962,17 +988,35 @@ async function main() {
|
|
|
962
988
|
console.log(`Endpoint: ${payload.endpoint}`);
|
|
963
989
|
console.log(`Pod: ${payload.pod_name}`);
|
|
964
990
|
console.log(`Actor: ${payload.actor_id}`);
|
|
991
|
+
console.log(`Linkage source: ${payload.linkage_source}`);
|
|
965
992
|
console.log(`Local identity: ${payload.local_identity.project_id}/${payload.local_identity.workspace_name}`);
|
|
966
993
|
console.log(`Remote identity: ${String(payload.remote_identity.project_id)}/${String(payload.remote_identity.workspace_name)}`);
|
|
967
994
|
console.log(`Remote document count: ${String(payload.document_count ?? "unknown")}`);
|
|
995
|
+
console.log(`Last imported at: ${String(payload.last_imported_at ?? "never")}`);
|
|
968
996
|
}
|
|
969
997
|
return 0;
|
|
970
998
|
}
|
|
971
999
|
if (subcommand === "push") {
|
|
972
1000
|
const documents = await collectWorkspaceDocuments(parsed.repoRoot);
|
|
973
|
-
|
|
974
|
-
|
|
1001
|
+
let importResult;
|
|
1002
|
+
try {
|
|
1003
|
+
importResult = validateImportResult(await client.importWorkspace(documents, true));
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1007
|
+
throw new Error(`Remote push failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
|
|
1008
|
+
}
|
|
1009
|
+
let workspace;
|
|
1010
|
+
try {
|
|
1011
|
+
workspace = await client.getWorkspace();
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1015
|
+
throw new Error(`Remote push could not verify remote state using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
|
|
1016
|
+
}
|
|
975
1017
|
assertRemoteIdentity(workspace, context.identity);
|
|
1018
|
+
const importedAt = new Date().toISOString();
|
|
1019
|
+
await updateRemoteConfigMetadata(parsed.repoRoot, { last_imported_at: importedAt });
|
|
976
1020
|
const payload = {
|
|
977
1021
|
status: "ok",
|
|
978
1022
|
command: "remote",
|
|
@@ -980,13 +1024,16 @@ async function main() {
|
|
|
980
1024
|
endpoint: context.mergedConfig.endpoint,
|
|
981
1025
|
pod_name: context.mergedConfig.pod_name,
|
|
982
1026
|
actor_id: context.mergedConfig.actor_id,
|
|
1027
|
+
linkage_source: context.linkageSource,
|
|
983
1028
|
project_id: context.identity.project_id,
|
|
984
1029
|
workspace_name: context.identity.workspace_name,
|
|
985
1030
|
local_document_count: documents.length,
|
|
986
|
-
imported: importResult.imported
|
|
987
|
-
created: importResult.created
|
|
988
|
-
updated: importResult.updated
|
|
989
|
-
deleted: importResult.deleted
|
|
1031
|
+
imported: importResult.imported,
|
|
1032
|
+
created: importResult.created,
|
|
1033
|
+
updated: importResult.updated,
|
|
1034
|
+
deleted: importResult.deleted,
|
|
1035
|
+
replace_missing: true,
|
|
1036
|
+
last_imported_at: importedAt,
|
|
990
1037
|
remote_document_count: extractWorkspaceIdentity(workspace).document_count,
|
|
991
1038
|
};
|
|
992
1039
|
if (output === "json") {
|
|
@@ -997,17 +1044,27 @@ async function main() {
|
|
|
997
1044
|
console.log(`Endpoint: ${payload.endpoint}`);
|
|
998
1045
|
console.log(`Pod: ${payload.pod_name}`);
|
|
999
1046
|
console.log(`Actor: ${payload.actor_id}`);
|
|
1047
|
+
console.log(`Linkage source: ${payload.linkage_source}`);
|
|
1048
|
+
console.log("Mode: remote reflects local (replace_missing=true)");
|
|
1000
1049
|
console.log(`Local document count: ${String(payload.local_document_count)}`);
|
|
1001
|
-
console.log(`Imported: ${String(payload.imported
|
|
1002
|
-
console.log(`Created: ${String(payload.created
|
|
1003
|
-
console.log(`Updated: ${String(payload.updated
|
|
1004
|
-
console.log(`Deleted: ${String(payload.deleted
|
|
1050
|
+
console.log(`Imported: ${String(payload.imported)}`);
|
|
1051
|
+
console.log(`Created: ${String(payload.created)}`);
|
|
1052
|
+
console.log(`Updated: ${String(payload.updated)}`);
|
|
1053
|
+
console.log(`Deleted: ${String(payload.deleted)}`);
|
|
1005
1054
|
console.log(`Remote document count: ${String(payload.remote_document_count ?? "unknown")}`);
|
|
1055
|
+
console.log(`Last imported at: ${payload.last_imported_at}`);
|
|
1006
1056
|
}
|
|
1007
1057
|
return 0;
|
|
1008
1058
|
}
|
|
1009
1059
|
if (subcommand === "ls") {
|
|
1010
|
-
|
|
1060
|
+
let snapshot;
|
|
1061
|
+
try {
|
|
1062
|
+
snapshot = await client.getSnapshot(parsed.prefix);
|
|
1063
|
+
}
|
|
1064
|
+
catch (error) {
|
|
1065
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1066
|
+
throw new Error(`Remote ls failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
|
|
1067
|
+
}
|
|
1011
1068
|
const paths = (snapshot.documents ?? [])
|
|
1012
1069
|
.map((document) => (typeof document.path === "string" ? document.path : ""))
|
|
1013
1070
|
.filter(Boolean)
|
|
@@ -1019,6 +1076,7 @@ async function main() {
|
|
|
1019
1076
|
endpoint: context.mergedConfig.endpoint,
|
|
1020
1077
|
pod_name: context.mergedConfig.pod_name,
|
|
1021
1078
|
actor_id: context.mergedConfig.actor_id,
|
|
1079
|
+
linkage_source: context.linkageSource,
|
|
1022
1080
|
prefix: parsed.prefix ?? null,
|
|
1023
1081
|
paths,
|
|
1024
1082
|
};
|
|
@@ -1043,7 +1101,17 @@ async function main() {
|
|
|
1043
1101
|
if (!documentPath.startsWith(".reffy/")) {
|
|
1044
1102
|
throw new Error("reffy remote cat requires a canonical path rooted at .reffy/");
|
|
1045
1103
|
}
|
|
1046
|
-
|
|
1104
|
+
let document;
|
|
1105
|
+
try {
|
|
1106
|
+
document = await client.getDocument(documentPath);
|
|
1107
|
+
}
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1110
|
+
throw new Error(`Remote cat failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
|
|
1111
|
+
}
|
|
1112
|
+
if (!document.document) {
|
|
1113
|
+
throw new Error(`Remote document not found using ${describeRemoteLinkage(context.mergedConfig)}: ${documentPath}`);
|
|
1114
|
+
}
|
|
1047
1115
|
const payload = {
|
|
1048
1116
|
status: "ok",
|
|
1049
1117
|
command: "remote",
|
|
@@ -1051,13 +1119,14 @@ async function main() {
|
|
|
1051
1119
|
endpoint: context.mergedConfig.endpoint,
|
|
1052
1120
|
pod_name: context.mergedConfig.pod_name,
|
|
1053
1121
|
actor_id: context.mergedConfig.actor_id,
|
|
1054
|
-
|
|
1122
|
+
linkage_source: context.linkageSource,
|
|
1123
|
+
document: document.document,
|
|
1055
1124
|
};
|
|
1056
1125
|
if (output === "json") {
|
|
1057
1126
|
printResult(output, payload);
|
|
1058
1127
|
}
|
|
1059
1128
|
else {
|
|
1060
|
-
const content = typeof document.document
|
|
1129
|
+
const content = typeof document.document.content === "string" ? document.document.content : "";
|
|
1061
1130
|
process.stdout.write(content);
|
|
1062
1131
|
if (!content.endsWith("\n")) {
|
|
1063
1132
|
process.stdout.write("\n");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function normalizeLines(content) {
|
|
4
|
+
return content
|
|
5
|
+
.split(/\r?\n/)
|
|
6
|
+
.map((line) => line.trim())
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
}
|
|
9
|
+
export async function ensureGitignoreEntries(repoRoot, entries) {
|
|
10
|
+
const gitignorePath = path.join(repoRoot, ".gitignore");
|
|
11
|
+
const current = await fs.readFile(gitignorePath, "utf8").catch(() => "");
|
|
12
|
+
const existing = new Set(normalizeLines(current));
|
|
13
|
+
const added = [];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const normalized = entry.trim();
|
|
16
|
+
if (!normalized || existing.has(normalized))
|
|
17
|
+
continue;
|
|
18
|
+
existing.add(normalized);
|
|
19
|
+
added.push(normalized);
|
|
20
|
+
}
|
|
21
|
+
if (added.length === 0) {
|
|
22
|
+
return { path: gitignorePath, added };
|
|
23
|
+
}
|
|
24
|
+
const nextLines = current.length > 0 ? current.replace(/\s*$/, "").split(/\r?\n/) : [];
|
|
25
|
+
if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== "") {
|
|
26
|
+
nextLines.push("");
|
|
27
|
+
}
|
|
28
|
+
nextLines.push(...added);
|
|
29
|
+
await fs.writeFile(gitignorePath, `${nextLines.join("\n")}\n`, "utf8");
|
|
30
|
+
return { path: gitignorePath, added };
|
|
31
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { MANIFEST_VERSION, allowedKindExtensions, validateManifest } from "./man
|
|
|
3
3
|
export { runDoctor } from "./doctor.js";
|
|
4
4
|
export { createPlanScaffold } from "./plan.js";
|
|
5
5
|
export { loadDotEnvIfPresent, parseDotEnv } from "./env.js";
|
|
6
|
+
export { ensureGitignoreEntries } from "./gitignore.js";
|
|
6
7
|
export { collectWorkspaceDocuments, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, resolveRemoteConfigPath, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
|
|
7
8
|
export { listSpecs, showSpec } from "./spec-runtime.js";
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export { MANIFEST_VERSION, allowedKindExtensions, validateManifest } from "./man
|
|
|
3
3
|
export { runDoctor } from "./doctor.js";
|
|
4
4
|
export { createPlanScaffold } from "./plan.js";
|
|
5
5
|
export { loadDotEnvIfPresent, parseDotEnv } from "./env.js";
|
|
6
|
+
export { ensureGitignoreEntries } from "./gitignore.js";
|
|
6
7
|
export { collectWorkspaceDocuments, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, resolveRemoteConfigPath, toCanonicalRemotePath, writeRemoteConfig, } from "./remote.js";
|
|
7
8
|
export { listSpecs, showSpec } from "./spec-runtime.js";
|
package/dist/remote.d.ts
CHANGED
|
@@ -17,10 +17,18 @@ export interface EnsureRemoteInitResult {
|
|
|
17
17
|
created_pod: boolean;
|
|
18
18
|
created_actor: boolean;
|
|
19
19
|
}
|
|
20
|
+
export interface ValidatedImportResult {
|
|
21
|
+
imported: number;
|
|
22
|
+
created: number;
|
|
23
|
+
updated: number;
|
|
24
|
+
deleted: number;
|
|
25
|
+
}
|
|
20
26
|
export declare function resolveRemoteConfigPath(repoRoot: string): string;
|
|
21
27
|
export declare function readRemoteConfig(repoRoot: string): Promise<RemoteLinkConfig | null>;
|
|
22
28
|
export declare function writeRemoteConfig(repoRoot: string, config: RemoteLinkConfig): Promise<string>;
|
|
29
|
+
export declare function updateRemoteConfigMetadata(repoRoot: string, patch: Partial<Pick<RemoteLinkConfig, "last_imported_at">>): Promise<RemoteLinkConfig | null>;
|
|
23
30
|
export declare function mergeRemoteConfig(stored: RemoteLinkConfig | null, overrides: Partial<Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">>): RemoteLinkConfig | null;
|
|
31
|
+
export declare function describeRemoteLinkage(config: Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">): string;
|
|
24
32
|
export declare class PaseoRemoteClient {
|
|
25
33
|
private readonly config;
|
|
26
34
|
constructor(config: Pick<RemoteLinkConfig, "endpoint" | "pod_name" | "actor_id">);
|
|
@@ -52,5 +60,6 @@ export declare function extractWorkspaceIdentity(summary: RemoteWorkspaceSummary
|
|
|
52
60
|
document_count?: number | null;
|
|
53
61
|
};
|
|
54
62
|
export declare function assertRemoteIdentity(summary: RemoteWorkspaceSummary, identity: WorkspaceIdentity): void;
|
|
63
|
+
export declare function validateImportResult(result: RemoteImportResult): ValidatedImportResult;
|
|
55
64
|
export declare function toCanonicalRemotePath(refsDir: string, filePath: string): string;
|
|
56
65
|
export declare function collectWorkspaceDocuments(repoRoot: string): Promise<RemoteDocument[]>;
|
package/dist/remote.js
CHANGED
|
@@ -62,6 +62,17 @@ export async function writeRemoteConfig(repoRoot, config) {
|
|
|
62
62
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
63
63
|
return configPath;
|
|
64
64
|
}
|
|
65
|
+
export async function updateRemoteConfigMetadata(repoRoot, patch) {
|
|
66
|
+
const existing = await readRemoteConfig(repoRoot);
|
|
67
|
+
if (!existing)
|
|
68
|
+
return null;
|
|
69
|
+
const next = {
|
|
70
|
+
...existing,
|
|
71
|
+
...patch,
|
|
72
|
+
};
|
|
73
|
+
await writeRemoteConfig(repoRoot, next);
|
|
74
|
+
return next;
|
|
75
|
+
}
|
|
65
76
|
export function mergeRemoteConfig(stored, overrides) {
|
|
66
77
|
if (!stored && !overrides.endpoint && !overrides.pod_name && !overrides.actor_id) {
|
|
67
78
|
return null;
|
|
@@ -81,6 +92,9 @@ export function mergeRemoteConfig(stored, overrides) {
|
|
|
81
92
|
last_imported_at: stored?.last_imported_at,
|
|
82
93
|
};
|
|
83
94
|
}
|
|
95
|
+
export function describeRemoteLinkage(config) {
|
|
96
|
+
return `endpoint=${config.endpoint} pod=${config.pod_name} actor=${config.actor_id}`;
|
|
97
|
+
}
|
|
84
98
|
async function http(url, init = {}) {
|
|
85
99
|
const headers = new Headers(init.headers ?? {});
|
|
86
100
|
if (!headers.has("Content-Type") && init.body) {
|
|
@@ -219,6 +233,19 @@ export function assertRemoteIdentity(summary, identity) {
|
|
|
219
233
|
throw new Error(`Remote identity mismatch: local=${identity.project_id}/${identity.workspace_name} remote=${String(remote.project_id)}/${String(remote.workspace_name)}`);
|
|
220
234
|
}
|
|
221
235
|
}
|
|
236
|
+
export function validateImportResult(result) {
|
|
237
|
+
const requiredKeys = ["imported", "created", "updated", "deleted"];
|
|
238
|
+
const missing = requiredKeys.filter((key) => typeof result[key] !== "number");
|
|
239
|
+
if (missing.length > 0) {
|
|
240
|
+
throw new Error(`Remote import response missing numeric field(s): ${missing.join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
imported: result.imported,
|
|
244
|
+
created: result.created,
|
|
245
|
+
updated: result.updated,
|
|
246
|
+
deleted: result.deleted,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
222
249
|
async function collectFiles(dirPath) {
|
|
223
250
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
224
251
|
const files = [];
|