@procedures/snapshot 0.11.0 → 0.12.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/CHANGELOG.md +29 -0
- package/README.md +10 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +13 -9
- package/dist/module.js.map +1 -1
- package/dist/procedures/snapshot/commands.d.ts +17 -9
- package/dist/procedures/snapshot/commands.d.ts.map +1 -1
- package/dist/procedures/snapshot/commands.js +28 -12
- package/dist/procedures/snapshot/commands.js.map +1 -1
- package/dist/procedures/snapshot/create.d.ts.map +1 -1
- package/dist/procedures/snapshot/create.js +20 -11
- package/dist/procedures/snapshot/create.js.map +1 -1
- package/dist/procedures/snapshot/diff.d.ts.map +1 -1
- package/dist/procedures/snapshot/diff.js +15 -9
- package/dist/procedures/snapshot/diff.js.map +1 -1
- package/package.json +7 -7
- package/src/module.ts +13 -10
- package/src/procedures/snapshot/commands.ts +26 -12
- package/src/procedures/snapshot/create.ts +19 -11
- package/src/procedures/snapshot/diff.ts +13 -9
|
@@ -11,9 +11,10 @@ import { tmpdir, hostname } from "node:os";
|
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import * as tar from "tar";
|
|
13
13
|
import type { ProcedureContext } from "@procedures/client";
|
|
14
|
+
import { isOutsideRoot, realPathIsInside, resolveRoot } from "@procedures/fs/root";
|
|
14
15
|
import { assertSnapshotName, isExcluded } from "./names.js";
|
|
15
16
|
import { readPart, sha256File } from "./archive-io.js";
|
|
16
|
-
import {
|
|
17
|
+
import { gitCall, pnpmVersion } from "./commands.js";
|
|
17
18
|
import type {
|
|
18
19
|
SnapshotCreateInput,
|
|
19
20
|
SnapshotCreateOutput,
|
|
@@ -49,7 +50,7 @@ export async function snapshotCreate(
|
|
|
49
50
|
);
|
|
50
51
|
|
|
51
52
|
// Get environment info
|
|
52
|
-
const environment = await getEnvironmentInfo(ctx.signal);
|
|
53
|
+
const environment = await getEnvironmentInfo(ctx.signal, ctx.fsRoot);
|
|
53
54
|
|
|
54
55
|
// Create metadata
|
|
55
56
|
const metadata: SnapshotMetadata = {
|
|
@@ -132,10 +133,10 @@ export async function snapshotCreate(
|
|
|
132
133
|
/**
|
|
133
134
|
* Get ecosystem repository paths from manifest
|
|
134
135
|
*/
|
|
135
|
-
async function getEcosystemPaths(
|
|
136
|
+
async function getEcosystemPaths(ctx: ProcedureContext): Promise<string[]> {
|
|
136
137
|
// TODO: Read from ecosystem manifest
|
|
137
|
-
// For now,
|
|
138
|
-
return [process.cwd()];
|
|
138
|
+
// For now, the root of the files under a root, else the current working directory
|
|
139
|
+
return [ctx.fsRoot ?? process.cwd()];
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
/**
|
|
@@ -152,8 +153,8 @@ async function getRepositoryInfo(
|
|
|
152
153
|
>(["git", "status"], { cwd: repoPath });
|
|
153
154
|
|
|
154
155
|
// The commit hash ("" for a repository with no commit) and the remote URL (none without "origin")
|
|
155
|
-
const commit = (await
|
|
156
|
-
const remoteUrl = await
|
|
156
|
+
const commit = (await gitCall<{ value: string }>(ctx, ["git", "revParse"], { cwd: repoPath }))?.value ?? "";
|
|
157
|
+
const remoteUrl = (await gitCall<{ url: string }>(ctx, ["git", "remote"], { cwd: repoPath, name: "origin" }))?.url;
|
|
157
158
|
|
|
158
159
|
// Get stash count
|
|
159
160
|
let stashCount = 0;
|
|
@@ -167,11 +168,13 @@ async function getRepositoryInfo(
|
|
|
167
168
|
// No stashes
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
// Get package name from package.json
|
|
171
|
+
// Get package name from package.json. Under a root, a package.json whose real path is outside
|
|
172
|
+
// the root (a symlink) is not read.
|
|
171
173
|
let name = basename(repoPath);
|
|
172
174
|
try {
|
|
173
175
|
const pkgPath = join(repoPath, "package.json");
|
|
174
|
-
|
|
176
|
+
const readable = ctx.fsRoot === undefined || (await realPathIsInside(await resolveRoot(ctx.fsRoot), pkgPath));
|
|
177
|
+
if (readable && existsSync(pkgPath)) {
|
|
175
178
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
176
179
|
name = pkg.name || name;
|
|
177
180
|
}
|
|
@@ -191,6 +194,8 @@ async function getRepositoryInfo(
|
|
|
191
194
|
behind: result.behind,
|
|
192
195
|
};
|
|
193
196
|
} catch (error) {
|
|
197
|
+
// A refusal of the root is not "no repository": the repository is there, and the root refuses it
|
|
198
|
+
if (isOutsideRoot(error)) throw error;
|
|
194
199
|
// Not a git repo or error
|
|
195
200
|
return {
|
|
196
201
|
path: repoPath,
|
|
@@ -208,11 +213,11 @@ async function getRepositoryInfo(
|
|
|
208
213
|
/**
|
|
209
214
|
* Get environment info
|
|
210
215
|
*/
|
|
211
|
-
async function getEnvironmentInfo(signal?: AbortSignal): Promise<SnapshotMetadata["environment"]> {
|
|
216
|
+
async function getEnvironmentInfo(signal?: AbortSignal, fsRoot?: string): Promise<SnapshotMetadata["environment"]> {
|
|
212
217
|
return {
|
|
213
218
|
os: process.platform,
|
|
214
219
|
nodeVersion: process.version,
|
|
215
|
-
pnpmVersion: await pnpmVersion(signal),
|
|
220
|
+
pnpmVersion: await pnpmVersion(signal, fsRoot),
|
|
216
221
|
username: process.env["USER"] || process.env["USERNAME"] || "unknown",
|
|
217
222
|
hostname: hostname(),
|
|
218
223
|
};
|
|
@@ -259,11 +264,14 @@ async function createArchive(
|
|
|
259
264
|
}
|
|
260
265
|
// heavy preset includes everything
|
|
261
266
|
|
|
267
|
+
// Tar reads only the listed folders from their parent. Without `follow`, it stores a symlink as
|
|
268
|
+
// a link and does not read its target: under a root, the archive has only files of the root.
|
|
262
269
|
await tar.create(
|
|
263
270
|
{
|
|
264
271
|
gzip: gzipOpts,
|
|
265
272
|
file: archivePath,
|
|
266
273
|
cwd,
|
|
274
|
+
follow: false,
|
|
267
275
|
// Whole path segments, and "*.ext" for file endings. (Before, `path.includes("dist")`
|
|
268
276
|
// left out src/distance.ts, and "*.log" matched nothing: deep dive DATA-3.)
|
|
269
277
|
filter: (path) => !isExcluded(path, excludePatterns),
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { basename } from "node:path";
|
|
9
|
-
import { gitText } from "./commands.js";
|
|
10
9
|
import type { ProcedureContext } from "@procedures/client";
|
|
10
|
+
import { isOutsideRoot } from "@procedures/fs/root";
|
|
11
|
+
import { gitCall } from "./commands.js";
|
|
11
12
|
import type {
|
|
12
13
|
SnapshotDiffInput,
|
|
13
14
|
SnapshotDiffOutput,
|
|
@@ -72,7 +73,7 @@ export async function snapshotDiff(
|
|
|
72
73
|
|
|
73
74
|
try {
|
|
74
75
|
// Get current branch (a failure is an error of this repository: the catch below)
|
|
75
|
-
const currentBranch = await
|
|
76
|
+
const currentBranch = (await gitCall<{ value: string }>(ctx, ["git", "revParse"], { cwd: repoPath, abbrevRef: true }))?.value;
|
|
76
77
|
if (currentBranch === undefined) throw new Error(`git cannot read the branch of ${repoPath}`);
|
|
77
78
|
|
|
78
79
|
if (currentBranch !== snapshotRepo.branch) {
|
|
@@ -83,7 +84,7 @@ export async function snapshotDiff(
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
// Get current commit
|
|
86
|
-
const currentCommit = await
|
|
87
|
+
const currentCommit = (await gitCall<{ value: string }>(ctx, ["git", "revParse"], { cwd: repoPath }))?.value;
|
|
87
88
|
if (currentCommit === undefined) throw new Error(`git cannot read the commit of ${repoPath}`);
|
|
88
89
|
|
|
89
90
|
if (currentCommit !== snapshotRepo.commit) {
|
|
@@ -99,12 +100,13 @@ export async function snapshotDiff(
|
|
|
99
100
|
if (!/^[0-9a-f]{7,64}$/i.test(snapshotRepo.commit)) {
|
|
100
101
|
throw new Error(`Not a commit hash: ${snapshotRepo.commit}`);
|
|
101
102
|
}
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
//
|
|
105
|
-
diff.filesChanged =
|
|
103
|
+
const changes = await gitCall<{ files: unknown[] }>(ctx, ["git", "diff"], { cwd: repoPath, ref: `${snapshotRepo.commit}..HEAD`, stat: true });
|
|
104
|
+
if (changes === undefined) throw new Error(`git cannot compare ${snapshotRepo.commit} with HEAD`);
|
|
105
|
+
// One entry for each file that changed, also a binary file
|
|
106
|
+
diff.filesChanged = changes.files.length;
|
|
106
107
|
totalFilesChanged += diff.filesChanged;
|
|
107
|
-
} catch {
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (isOutsideRoot(error)) throw error;
|
|
108
110
|
// Snapshot commit may not exist locally
|
|
109
111
|
diff.filesChanged = -1;
|
|
110
112
|
}
|
|
@@ -126,7 +128,9 @@ export async function snapshotDiff(
|
|
|
126
128
|
) {
|
|
127
129
|
reposChanged++;
|
|
128
130
|
}
|
|
129
|
-
} catch {
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// A refusal of the root is not "no repository": the repository is there, and the root refuses it
|
|
133
|
+
if (isOutsideRoot(error)) throw error;
|
|
130
134
|
// Not a git repo or error
|
|
131
135
|
diff.filesChanged = -1;
|
|
132
136
|
reposChanged++;
|