@peers-app/peers-sdk 0.23.7 → 0.23.9
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.
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import type { DataContext } from "../context/data-context";
|
|
2
|
-
import type { ICreatePackageOpts, ICreatePackageResult, IPackageInstallerDeps } from "./types";
|
|
2
|
+
import type { IAddOrUpdatePackageResult, ICreatePackageOpts, ICreatePackageResult, IPackageInstallerDeps, ITemplateCacheResult, TemplateCacheRefreshFailure } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Normalize `addOrUpdatePackage` RPC results.
|
|
5
|
+
* Older hosts return a bare packageId string; current hosts return
|
|
6
|
+
* {@link IAddOrUpdatePackageResult}.
|
|
7
|
+
*/
|
|
8
|
+
export declare function unwrapAddOrUpdatePackageResult(result: string | IAddOrUpdatePackageResult): IAddOrUpdatePackageResult;
|
|
3
9
|
/**
|
|
4
10
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
5
11
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
6
12
|
*/
|
|
7
13
|
export declare function nameToFolderSlug(name: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Classify a failed `git pull` of the template cache as a network outage versus
|
|
16
|
+
* a local/git problem (dirty tree, auth, merge conflict).
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyTemplatePullFailure(error: unknown): TemplateCacheRefreshFailure;
|
|
8
19
|
/**
|
|
9
20
|
* Ensure the package template is available locally.
|
|
10
21
|
* On first call, clones the template repo into a dot-prefixed cache directory.
|
|
11
|
-
* On
|
|
12
|
-
*
|
|
22
|
+
* On later calls, attempts `git pull`. A failed pull does not block create;
|
|
23
|
+
* the existing cache is used and a warning is returned so the host can show it.
|
|
24
|
+
* @returns Cache path and an optional stale-template warning.
|
|
13
25
|
*/
|
|
14
|
-
export declare function ensureTemplateCache(deps: IPackageInstallerDeps, templateRepo: string): Promise<
|
|
26
|
+
export declare function ensureTemplateCache(deps: IPackageInstallerDeps, templateRepo: string): Promise<ITemplateCacheResult>;
|
|
15
27
|
/**
|
|
16
28
|
* Resolve (or create) the Ed25519 publish public key for a package.
|
|
17
29
|
*
|
|
@@ -44,8 +56,8 @@ export declare function importExistingPackage(dataContext: DataContext, deps: IP
|
|
|
44
56
|
* Create a new Peers package from the package template.
|
|
45
57
|
*
|
|
46
58
|
* Validates git/Node.js/npm first so a missing toolchain does not leave a
|
|
47
|
-
* half-copied package directory. Then clones the template repo, patches
|
|
48
|
-
* and metadata, runs npm install + build, initializes a fresh git repo, and
|
|
59
|
+
* half-copied package directory. Then clones the template repo, patches
|
|
60
|
+
* isolated-package IDs and metadata, runs npm install + build, initializes a fresh git repo, and
|
|
49
61
|
* delegates to {@link installPackageFromBundles} to register the dev bundle.
|
|
50
62
|
*
|
|
51
63
|
* When `location` already exists and contains a valid Peers package manifest,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.unwrapAddOrUpdatePackageResult = unwrapAddOrUpdatePackageResult;
|
|
3
4
|
exports.nameToFolderSlug = nameToFolderSlug;
|
|
5
|
+
exports.classifyTemplatePullFailure = classifyTemplatePullFailure;
|
|
4
6
|
exports.ensureTemplateCache = ensureTemplateCache;
|
|
5
7
|
exports.ensurePackageSigningKey = ensurePackageSigningKey;
|
|
6
8
|
exports.importExistingPackage = importExistingPackage;
|
|
@@ -15,6 +17,24 @@ const package_dev_tools_1 = require("./package-dev-tools");
|
|
|
15
17
|
const package_installer_1 = require("./package-installer");
|
|
16
18
|
const DEFAULT_TEMPLATE_REPO = "https://github.com/peers-app/peers-package-template.git";
|
|
17
19
|
const TEMPLATE_CACHE_DIR = ".peers-package-template";
|
|
20
|
+
function patchTemplateTokens(contents, values) {
|
|
21
|
+
return contents
|
|
22
|
+
.replaceAll("<package-name>", values.packageName)
|
|
23
|
+
.replaceAll("<package-id>", values.packageId)
|
|
24
|
+
.replaceAll("<app-screen-id>", values.appScreenId)
|
|
25
|
+
.replaceAll("<contract-id>", values.contractId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalize `addOrUpdatePackage` RPC results.
|
|
29
|
+
* Older hosts return a bare packageId string; current hosts return
|
|
30
|
+
* {@link IAddOrUpdatePackageResult}.
|
|
31
|
+
*/
|
|
32
|
+
function unwrapAddOrUpdatePackageResult(result) {
|
|
33
|
+
if (typeof result === "string") {
|
|
34
|
+
return { packageId: result };
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
18
38
|
/**
|
|
19
39
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
20
40
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
@@ -24,26 +44,67 @@ function nameToFolderSlug(name) {
|
|
|
24
44
|
slug = slug.replace(/\s+/g, " ").trim();
|
|
25
45
|
return (0, utils_1.camelCaseToHyphens)(slug);
|
|
26
46
|
}
|
|
47
|
+
const OFFLINE_PULL_PATTERN = /unable to access|could not resolve host|failed to connect|connection (refused|timed out|reset)|network is unreachable|enotfound|etimedout|eai_again|offline|ssl connect error|could not fetch/i;
|
|
48
|
+
/**
|
|
49
|
+
* Classify a failed `git pull` of the template cache as a network outage versus
|
|
50
|
+
* a local/git problem (dirty tree, auth, merge conflict).
|
|
51
|
+
*/
|
|
52
|
+
function classifyTemplatePullFailure(error) {
|
|
53
|
+
const text = execErrorText(error);
|
|
54
|
+
return OFFLINE_PULL_PATTERN.test(text) ? "offline" : "could-not-refresh";
|
|
55
|
+
}
|
|
56
|
+
function execErrorText(error) {
|
|
57
|
+
if (!(error instanceof Error)) {
|
|
58
|
+
return String(error);
|
|
59
|
+
}
|
|
60
|
+
const extra = "stderr" in error && typeof error.stderr === "string" ? error.stderr : "";
|
|
61
|
+
return [error.message, extra].filter(Boolean).join("\n");
|
|
62
|
+
}
|
|
63
|
+
function firstUsefulErrorLine(text) {
|
|
64
|
+
const line = text
|
|
65
|
+
.split(/\r?\n/)
|
|
66
|
+
.map((entry) => entry.trim())
|
|
67
|
+
.find((entry) => entry.length > 0);
|
|
68
|
+
if (!line) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
return line.length > 240 ? `${line.slice(0, 237)}...` : line;
|
|
72
|
+
}
|
|
73
|
+
function templateCacheWarning(failure, error) {
|
|
74
|
+
const detail = firstUsefulErrorLine(execErrorText(error));
|
|
75
|
+
const detailSuffix = detail ? ` ${detail}` : "";
|
|
76
|
+
if (failure === "offline") {
|
|
77
|
+
return ("This package was created from a cached template because the template repository could not be reached (offline or network error). It may not match the latest isolate package scaffold." +
|
|
78
|
+
detailSuffix);
|
|
79
|
+
}
|
|
80
|
+
return ("This package was created from a cached template because git pull could not update ~/peers/packages/.peers-package-template. It may be a stale version. Local changes in that cache directory are the usual cause." +
|
|
81
|
+
detailSuffix);
|
|
82
|
+
}
|
|
27
83
|
/**
|
|
28
84
|
* Ensure the package template is available locally.
|
|
29
85
|
* On first call, clones the template repo into a dot-prefixed cache directory.
|
|
30
|
-
* On
|
|
31
|
-
*
|
|
86
|
+
* On later calls, attempts `git pull`. A failed pull does not block create;
|
|
87
|
+
* the existing cache is used and a warning is returned so the host can show it.
|
|
88
|
+
* @returns Cache path and an optional stale-template warning.
|
|
32
89
|
*/
|
|
33
90
|
async function ensureTemplateCache(deps, templateRepo) {
|
|
34
91
|
const cachePath = deps.resolvePath(deps.packagesRootDir, TEMPLATE_CACHE_DIR);
|
|
35
92
|
if (await deps.fs.exists(cachePath)) {
|
|
36
93
|
try {
|
|
37
94
|
await deps.shell.exec("git pull", { cwd: cachePath });
|
|
95
|
+
return { cachePath };
|
|
38
96
|
}
|
|
39
|
-
catch {
|
|
40
|
-
|
|
97
|
+
catch (error) {
|
|
98
|
+
const refreshFailure = classifyTemplatePullFailure(error);
|
|
99
|
+
return {
|
|
100
|
+
cachePath,
|
|
101
|
+
refreshFailure,
|
|
102
|
+
warning: templateCacheWarning(refreshFailure, error),
|
|
103
|
+
};
|
|
41
104
|
}
|
|
42
105
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
return cachePath;
|
|
106
|
+
await deps.shell.exec(`git clone ${templateRepo} ${cachePath}`);
|
|
107
|
+
return { cachePath };
|
|
47
108
|
}
|
|
48
109
|
/**
|
|
49
110
|
* Resolve (or create) the Ed25519 publish public key for a package.
|
|
@@ -172,8 +233,8 @@ async function importExistingPackage(dataContext, deps, opts) {
|
|
|
172
233
|
* Create a new Peers package from the package template.
|
|
173
234
|
*
|
|
174
235
|
* Validates git/Node.js/npm first so a missing toolchain does not leave a
|
|
175
|
-
* half-copied package directory. Then clones the template repo, patches
|
|
176
|
-
* and metadata, runs npm install + build, initializes a fresh git repo, and
|
|
236
|
+
* half-copied package directory. Then clones the template repo, patches
|
|
237
|
+
* isolated-package IDs and metadata, runs npm install + build, initializes a fresh git repo, and
|
|
177
238
|
* delegates to {@link installPackageFromBundles} to register the dev bundle.
|
|
178
239
|
*
|
|
179
240
|
* When `location` already exists and contains a valid Peers package manifest,
|
|
@@ -195,6 +256,7 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
195
256
|
const contractId = (0, utils_1.newid)();
|
|
196
257
|
const appScreenId = (0, utils_1.newid)();
|
|
197
258
|
const templateRepo = opts.templateRepo ?? DEFAULT_TEMPLATE_REPO;
|
|
259
|
+
let templateWarning;
|
|
198
260
|
// Get template contents into location
|
|
199
261
|
if (opts.templateRepo && opts.templateRepo !== DEFAULT_TEMPLATE_REPO) {
|
|
200
262
|
// Custom template — clone directly (no caching)
|
|
@@ -202,8 +264,9 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
202
264
|
}
|
|
203
265
|
else {
|
|
204
266
|
// Default template — use local cache for speed and offline support
|
|
205
|
-
const
|
|
206
|
-
|
|
267
|
+
const templateCache = await ensureTemplateCache(deps, templateRepo);
|
|
268
|
+
templateWarning = templateCache.warning;
|
|
269
|
+
await deps.shell.exec(`cp -r ${templateCache.cachePath}/. ${location}`);
|
|
207
270
|
}
|
|
208
271
|
// Patch package.json
|
|
209
272
|
const packageJsonPath = deps.resolvePath(location, "package.json");
|
|
@@ -216,16 +279,23 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
216
279
|
}
|
|
217
280
|
delete packageObj.repository;
|
|
218
281
|
await deps.fs.writeFile(packageJsonPath, JSON.stringify(packageObj, null, 2));
|
|
219
|
-
// Patch
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
282
|
+
// Patch the isolated template's canonical IDs. consts.ts remains supported for custom
|
|
283
|
+
// legacy templates created before isolated package artifacts became the default.
|
|
284
|
+
const templateValues = {
|
|
285
|
+
packageId,
|
|
286
|
+
packageName: opts.name,
|
|
287
|
+
appScreenId,
|
|
288
|
+
contractId,
|
|
289
|
+
};
|
|
290
|
+
for (const relativePath of [
|
|
291
|
+
["src", "ids.json"],
|
|
292
|
+
["src", "consts.ts"],
|
|
293
|
+
]) {
|
|
294
|
+
const templatePath = deps.resolvePath(location, ...relativePath);
|
|
295
|
+
if (await deps.fs.exists(templatePath)) {
|
|
296
|
+
const contents = await deps.fs.readFile(templatePath);
|
|
297
|
+
await deps.fs.writeFile(templatePath, patchTemplateTokens(contents, templateValues));
|
|
298
|
+
}
|
|
229
299
|
}
|
|
230
300
|
// Initialize as a new project: remove template git history, install, build, fresh git
|
|
231
301
|
await deps.shell.exec("rm -rf .git", { cwd: location });
|
|
@@ -259,5 +329,6 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
259
329
|
packageId,
|
|
260
330
|
localPath: location,
|
|
261
331
|
packageVersion: result.packageVersion,
|
|
332
|
+
templateWarning,
|
|
262
333
|
};
|
|
263
334
|
}
|
|
@@ -83,6 +83,22 @@ beforeEach(() => {
|
|
|
83
83
|
publicBoxKey: "test-box-key",
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
|
+
describe("unwrapAddOrUpdatePackageResult", () => {
|
|
87
|
+
it("accepts a legacy packageId string", () => {
|
|
88
|
+
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)("00mnkoyo7mizav6tseubjzr3a")).toEqual({
|
|
89
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
it("passes through a structured result including a template warning", () => {
|
|
93
|
+
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)({
|
|
94
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
95
|
+
warning: "stale template",
|
|
96
|
+
})).toEqual({
|
|
97
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
98
|
+
warning: "stale template",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
86
102
|
describe("nameToFolderSlug", () => {
|
|
87
103
|
it("converts a simple name", () => {
|
|
88
104
|
expect((0, package_creator_1.nameToFolderSlug)("My Widget")).toBe("my-widget");
|
|
@@ -122,24 +138,27 @@ describe("createPackage", () => {
|
|
|
122
138
|
peers: {},
|
|
123
139
|
repository: { url: "https://github.com/peers-app/peers-package-template" },
|
|
124
140
|
});
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
const idsContent = JSON.stringify({
|
|
142
|
+
packageId: "<package-id>",
|
|
143
|
+
packageName: "<package-name>",
|
|
144
|
+
screenId: "<app-screen-id>",
|
|
145
|
+
contractId: "<contract-id>",
|
|
146
|
+
stateObservableName: "state",
|
|
147
|
+
});
|
|
129
148
|
const deps = makeMockDeps({});
|
|
130
149
|
const dc = mockDataContext();
|
|
131
|
-
// Simulate: location
|
|
150
|
+
// Simulate: location and cache do not exist; ids.json exists after the template copy.
|
|
132
151
|
deps.fs.exists.mockImplementation(async (path) => {
|
|
133
152
|
if (path === "/packages/my-widget")
|
|
134
153
|
return false;
|
|
135
154
|
if (path === "/packages/.peers-package-template")
|
|
136
155
|
return false;
|
|
137
|
-
if (path.includes("
|
|
156
|
+
if (path.includes("ids.json"))
|
|
138
157
|
return true;
|
|
139
158
|
return false;
|
|
140
159
|
});
|
|
141
160
|
deps.fs.readJson.mockResolvedValue(JSON.parse(templatePkgJson));
|
|
142
|
-
deps.fs.readFile.mockResolvedValue(
|
|
161
|
+
deps.fs.readFile.mockResolvedValue(idsContent);
|
|
143
162
|
const pvarSetter = jest.fn();
|
|
144
163
|
mockGroupDeviceVar.mockReturnValue(pvarSetter);
|
|
145
164
|
const mockPv = { packageVersionId: "pv001", packageId: "pkg001" };
|
|
@@ -167,15 +186,14 @@ describe("createPackage", () => {
|
|
|
167
186
|
expect(writtenPkgJson.author).toBe("Test User");
|
|
168
187
|
expect(writtenPkgJson.peers.packageId).toMatch(/^[a-z0-9]{25}$/);
|
|
169
188
|
expect(writtenPkgJson.repository).toBeUndefined();
|
|
170
|
-
// Verify
|
|
171
|
-
const
|
|
172
|
-
expect(
|
|
173
|
-
const
|
|
174
|
-
expect(
|
|
175
|
-
expect(
|
|
176
|
-
expect(
|
|
177
|
-
expect(
|
|
178
|
-
expect(writtenConsts).toContain("My Widget");
|
|
189
|
+
// Verify the isolated package ID source was patched.
|
|
190
|
+
const idsWriteCall = deps.fs.writeFile.mock.calls.find((c) => c[0].includes("ids.json"));
|
|
191
|
+
expect(idsWriteCall).toBeTruthy();
|
|
192
|
+
const writtenIds = JSON.parse(idsWriteCall[1]);
|
|
193
|
+
expect(writtenIds.packageId).toMatch(/^[a-z0-9]{25}$/);
|
|
194
|
+
expect(writtenIds.contractId).toMatch(/^[a-z0-9]{25}$/);
|
|
195
|
+
expect(writtenIds.screenId).toMatch(/^[a-z0-9]{25}$/);
|
|
196
|
+
expect(writtenIds.packageName).toBe("My Widget");
|
|
179
197
|
// Verify initialization commands
|
|
180
198
|
expect(deps.shell.exec).toHaveBeenCalledWith("rm -rf .git", { cwd: "/packages/my-widget" });
|
|
181
199
|
expect(deps.shell.exec).toHaveBeenCalledWith("npm install", { cwd: "/packages/my-widget" });
|
|
@@ -199,6 +217,37 @@ describe("createPackage", () => {
|
|
|
199
217
|
});
|
|
200
218
|
expect(result.localPath).toBe("/packages/my-widget");
|
|
201
219
|
expect(result.packageVersion).toBe(mockPv);
|
|
220
|
+
expect(result.templateWarning).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
it("returns a stale-template warning when the cached template cannot be refreshed", async () => {
|
|
223
|
+
const shellExec = jest.fn(async (cmd) => {
|
|
224
|
+
if (cmd === "git pull") {
|
|
225
|
+
throw new Error("error: Your local changes to the following files would be overwritten by merge:\n\t.gitignore");
|
|
226
|
+
}
|
|
227
|
+
return { stdout: "", stderr: "" };
|
|
228
|
+
});
|
|
229
|
+
const deps = makeMockDeps({}, shellExec);
|
|
230
|
+
const dc = mockDataContext();
|
|
231
|
+
deps.fs.exists.mockImplementation(async (path) => {
|
|
232
|
+
if (path === "/packages/desk")
|
|
233
|
+
return false;
|
|
234
|
+
if (path === "/packages/.peers-package-template")
|
|
235
|
+
return true;
|
|
236
|
+
return false;
|
|
237
|
+
});
|
|
238
|
+
deps.fs.readJson.mockResolvedValue({ name: "template", peers: {} });
|
|
239
|
+
const pvarSetter = jest.fn();
|
|
240
|
+
mockGroupDeviceVar.mockReturnValue(pvarSetter);
|
|
241
|
+
mockInstallPackageFromBundles.mockResolvedValue({
|
|
242
|
+
packageVersion: { packageVersionId: "pv" },
|
|
243
|
+
loaded: undefined,
|
|
244
|
+
unchanged: false,
|
|
245
|
+
});
|
|
246
|
+
const result = await (0, package_creator_1.createPackage)(dc, deps, { name: "Desk" });
|
|
247
|
+
expect(result.templateWarning).toMatch(/stale version/i);
|
|
248
|
+
expect(result.templateWarning).toMatch(/local changes/i);
|
|
249
|
+
expect(result.localPath).toBe("/packages/desk");
|
|
250
|
+
expect(deps.shell.exec).toHaveBeenCalledWith("cp -r /packages/.peers-package-template/. /packages/desk");
|
|
202
251
|
});
|
|
203
252
|
it("registers metadata, path, and signing key when importing an existing package directory", async () => {
|
|
204
253
|
const deps = makeMockDeps({});
|
|
@@ -445,14 +494,16 @@ describe("ensureTemplateCache", () => {
|
|
|
445
494
|
const deps = makeMockDeps({});
|
|
446
495
|
deps.fs.exists.mockResolvedValue(false);
|
|
447
496
|
const result = await (0, package_creator_1.ensureTemplateCache)(deps, "https://github.com/peers-app/peers-package-template.git");
|
|
448
|
-
expect(result).toBe("/packages/.peers-package-template");
|
|
497
|
+
expect(result.cachePath).toBe("/packages/.peers-package-template");
|
|
498
|
+
expect(result.warning).toBeUndefined();
|
|
449
499
|
expect(deps.shell.exec).toHaveBeenCalledWith("git clone https://github.com/peers-app/peers-package-template.git /packages/.peers-package-template");
|
|
450
500
|
});
|
|
451
501
|
it("pulls latest when cache directory already exists", async () => {
|
|
452
502
|
const deps = makeMockDeps({});
|
|
453
503
|
deps.fs.exists.mockResolvedValue(true);
|
|
454
504
|
const result = await (0, package_creator_1.ensureTemplateCache)(deps, "https://github.com/peers-app/peers-package-template.git");
|
|
455
|
-
expect(result).toBe("/packages/.peers-package-template");
|
|
505
|
+
expect(result.cachePath).toBe("/packages/.peers-package-template");
|
|
506
|
+
expect(result.warning).toBeUndefined();
|
|
456
507
|
expect(deps.shell.exec).toHaveBeenCalledWith("git pull", {
|
|
457
508
|
cwd: "/packages/.peers-package-template",
|
|
458
509
|
});
|
|
@@ -460,16 +511,43 @@ describe("ensureTemplateCache", () => {
|
|
|
460
511
|
const execCalls = deps.shell.exec.mock.calls.map((c) => c[0]);
|
|
461
512
|
expect(execCalls).not.toContain(expect.stringContaining("git clone"));
|
|
462
513
|
});
|
|
463
|
-
it("
|
|
514
|
+
it("returns an offline warning when pull cannot reach the remote", async () => {
|
|
464
515
|
const shellExec = jest.fn(async (cmd) => {
|
|
465
|
-
if (cmd === "git pull")
|
|
466
|
-
throw new Error("fatal: unable to access
|
|
516
|
+
if (cmd === "git pull") {
|
|
517
|
+
throw new Error("fatal: unable to access 'https://github.com/peers-app/peers-package-template.git'");
|
|
518
|
+
}
|
|
467
519
|
return { stdout: "", stderr: "" };
|
|
468
520
|
});
|
|
469
521
|
const deps = makeMockDeps({}, shellExec);
|
|
470
522
|
deps.fs.exists.mockResolvedValue(true);
|
|
471
523
|
const result = await (0, package_creator_1.ensureTemplateCache)(deps, "https://github.com/peers-app/peers-package-template.git");
|
|
472
|
-
|
|
473
|
-
expect(result).toBe("
|
|
524
|
+
expect(result.cachePath).toBe("/packages/.peers-package-template");
|
|
525
|
+
expect(result.refreshFailure).toBe("offline");
|
|
526
|
+
expect(result.warning).toMatch(/could not be reached/i);
|
|
527
|
+
});
|
|
528
|
+
it("returns a could-not-refresh warning when pull fails for local git reasons", async () => {
|
|
529
|
+
const shellExec = jest.fn(async (cmd) => {
|
|
530
|
+
if (cmd === "git pull") {
|
|
531
|
+
throw new Error("error: Your local changes to the following files would be overwritten by merge:\n\t.gitignore");
|
|
532
|
+
}
|
|
533
|
+
return { stdout: "", stderr: "" };
|
|
534
|
+
});
|
|
535
|
+
const deps = makeMockDeps({}, shellExec);
|
|
536
|
+
deps.fs.exists.mockResolvedValue(true);
|
|
537
|
+
const result = await (0, package_creator_1.ensureTemplateCache)(deps, "https://github.com/peers-app/peers-package-template.git");
|
|
538
|
+
expect(result.cachePath).toBe("/packages/.peers-package-template");
|
|
539
|
+
expect(result.refreshFailure).toBe("could-not-refresh");
|
|
540
|
+
expect(result.warning).toMatch(/stale version/i);
|
|
541
|
+
expect(result.warning).toMatch(/overwritten by merge/i);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
describe("classifyTemplatePullFailure", () => {
|
|
545
|
+
it("treats unreachable remotes as offline", () => {
|
|
546
|
+
expect((0, package_creator_1.classifyTemplatePullFailure)(new Error("fatal: unable to access remote"))).toBe("offline");
|
|
547
|
+
expect((0, package_creator_1.classifyTemplatePullFailure)(new Error("Could not resolve host: github.com"))).toBe("offline");
|
|
548
|
+
});
|
|
549
|
+
it("treats dirty trees and auth problems as could-not-refresh", () => {
|
|
550
|
+
expect((0, package_creator_1.classifyTemplatePullFailure)(new Error("Your local changes to the following files would be overwritten by merge"))).toBe("could-not-refresh");
|
|
551
|
+
expect((0, package_creator_1.classifyTemplatePullFailure)(new Error("Permission denied (publickey)"))).toBe("could-not-refresh");
|
|
474
552
|
});
|
|
475
553
|
});
|
|
@@ -84,11 +84,40 @@ export interface ICreatePackageOpts {
|
|
|
84
84
|
*/
|
|
85
85
|
personalContext?: import("../context/data-context").DataContext;
|
|
86
86
|
}
|
|
87
|
+
/** Why a `git pull` of the cached package template did not succeed. */
|
|
88
|
+
export type TemplateCacheRefreshFailure = "offline" | "could-not-refresh";
|
|
89
|
+
/** Local template cache path plus a non-fatal refresh warning when present. */
|
|
90
|
+
export interface ITemplateCacheResult {
|
|
91
|
+
/** Directory copied from when scaffolding a new package. */
|
|
92
|
+
cachePath: string;
|
|
93
|
+
/** Set when `git pull` failed and the existing cache was used anyway. */
|
|
94
|
+
refreshFailure?: TemplateCacheRefreshFailure;
|
|
95
|
+
/** User-facing explanation when {@link refreshFailure} is set. */
|
|
96
|
+
warning?: string;
|
|
97
|
+
}
|
|
87
98
|
/** Result of {@link createPackage}. */
|
|
88
99
|
export interface ICreatePackageResult {
|
|
100
|
+
/** New or imported package ID. */
|
|
89
101
|
packageId: string;
|
|
102
|
+
/** On-disk package directory. */
|
|
90
103
|
localPath: string;
|
|
104
|
+
/** Installed local/dev package version record. */
|
|
91
105
|
packageVersion: IPackageVersion;
|
|
106
|
+
/**
|
|
107
|
+
* Present when the default template cache could not be refreshed and a
|
|
108
|
+
* possibly stale copy was used. Creation still succeeded.
|
|
109
|
+
*/
|
|
110
|
+
templateWarning?: string;
|
|
111
|
+
}
|
|
112
|
+
/** Host/RPC result for adding, creating, or updating a package. */
|
|
113
|
+
export interface IAddOrUpdatePackageResult {
|
|
114
|
+
/** Package that was created, imported, cloned, or updated. */
|
|
115
|
+
packageId: string;
|
|
116
|
+
/**
|
|
117
|
+
* Non-fatal notice after a successful local create (for example a stale
|
|
118
|
+
* template cache). Undefined on update, clone, and import of an existing directory.
|
|
119
|
+
*/
|
|
120
|
+
warning?: string;
|
|
92
121
|
}
|
|
93
122
|
/** Options for {@link clonePackage}. */
|
|
94
123
|
export interface IClonePackageOpts {
|
|
@@ -103,8 +132,12 @@ export interface IClonePackageOpts {
|
|
|
103
132
|
}
|
|
104
133
|
/** Result of {@link clonePackage}. */
|
|
105
134
|
export interface IClonePackageResult {
|
|
135
|
+
/** Imported package ID from the cloned repo. */
|
|
106
136
|
packageId: string;
|
|
137
|
+
/** On-disk clone directory. */
|
|
107
138
|
localPath: string;
|
|
139
|
+
/** Git URL that was cloned. */
|
|
108
140
|
remoteRepo: string;
|
|
141
|
+
/** Installed local/dev package version record. */
|
|
109
142
|
packageVersion: IPackageVersion;
|
|
110
143
|
}
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { IDeviceDatabaseQueryResult, IDeviceOperationStatus, IDeviceToolRun
|
|
|
2
2
|
import type { ITool } from "./data/tools";
|
|
3
3
|
import type { DevicePairingCeremonyAction, DevicePairingStartDestinationInput, DevicePairingStartSourceResult, DevicePairingState } from "./device-pairing";
|
|
4
4
|
import type { IConsoleLog } from "./logging/console-logs.table";
|
|
5
|
+
import type { IAddOrUpdatePackageResult } from "./package-installer/types";
|
|
5
6
|
import type { IAppNav } from "./types/app-nav";
|
|
6
7
|
/** Named event payload sent across the frontend RPC bridge. */
|
|
7
8
|
export interface IEventData<T = any> {
|
|
@@ -61,7 +62,7 @@ export declare const rpcServerCalls: {
|
|
|
61
62
|
packageLocation?: string;
|
|
62
63
|
update?: boolean;
|
|
63
64
|
linkRemote?: boolean;
|
|
64
|
-
}) => Promise<string>;
|
|
65
|
+
}) => Promise<string | IAddOrUpdatePackageResult>;
|
|
65
66
|
seedBundledPeersCore: (dataContextId?: string) => Promise<boolean>;
|
|
66
67
|
setUserIdAndSecretKey: (userId: string, secretKey: string) => Promise<void>;
|
|
67
68
|
getUserId: () => Promise<string | undefined>;
|