@avi2dg/checks 0.22.0 → 0.24.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/scripts/vendor.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Console, Effect, FileSystem, Option, Path, Schema } from "effect";
3
3
  import { git } from "./git.ts";
4
- import { runMain, Usage } from "./main.ts";
4
+ import { runMain } from "./main.ts";
5
+ import { librariesFrom, NAME, OPENER, type Library } from "./vendor-args.ts";
5
6
 
6
- const NAME = "checks-vendor";
7
- const USAGE = `usage: ${NAME} [--library <name> --package <package> --repository <remote> --tag <template> [--path <manifest>]]...`;
8
7
  const CACHE_HOME = ".cache/avi2dg-checks";
9
8
  const RECORD_SUFFIX = ".commit";
10
9
  const LINKS = "repos";
@@ -21,55 +20,6 @@ export class Unreachable extends Schema.TaggedError<Unreachable>()("Unreachable"
21
20
  message: Schema.String,
22
21
  }) {}
23
22
 
24
- const Library = Schema.Struct({
25
- name: Schema.String.check(Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)),
26
- package: Schema.NonEmptyString,
27
- repository: Schema.NonEmptyString,
28
- tag: Schema.String.check(Schema.isPattern(/\{version\}/)),
29
- path: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^(?:[\w.@+-]+\/)*[\w.@+-]+\.\w+$/))),
30
- });
31
- type Library = typeof Library.Type;
32
-
33
- const Libraries = Schema.Array(Library).check(
34
- Schema.makeFilter((libraries) => {
35
- const names = libraries.map(({ name }) => name);
36
- const repeated = names.find((name, index) => names.indexOf(name) !== index);
37
- return repeated === undefined || `--library ${repeated} appears more than once`;
38
- }),
39
- );
40
-
41
- const OPENER = "--library";
42
- const FIELDS = new Map<string, keyof Library>([
43
- ["--package", "package"],
44
- ["--repository", "repository"],
45
- ["--tag", "tag"],
46
- ["--path", "path"],
47
- ]);
48
-
49
- function misuse(message: string): Usage {
50
- return new Usage({ message: `${message}\n${USAGE}` });
51
- }
52
-
53
- const librariesFrom = Effect.fn("librariesFrom")(function* (args: readonly string[]) {
54
- const groups: Record<string, string>[] = [];
55
- for (let index = 0; index < args.length; index += 2) {
56
- const flag = args[index] ?? "";
57
- const value = args[index + 1];
58
- if (value === undefined || value.startsWith("--")) return yield* misuse(`${flag} takes a value`);
59
- if (flag === OPENER) {
60
- groups.push({ name: value });
61
- continue;
62
- }
63
- const field = FIELDS.get(flag);
64
- const current = groups.at(-1);
65
- if (field === undefined) return yield* misuse(`${flag} is not an argument`);
66
- if (current === undefined) return yield* misuse(`${flag} comes before any ${OPENER}`);
67
- if (field in current) return yield* misuse(`${flag} appears twice for ${OPENER} ${current["name"]}`);
68
- current[field] = value;
69
- }
70
- return yield* Schema.decodeUnknownEffect(Libraries)(groups).pipe(Effect.mapError((cause) => misuse(cause.message)));
71
- });
72
-
73
23
  export function tagFor(template: string, version: string): string {
74
24
  return template.replaceAll(VERSION_TOKEN, version);
75
25
  }
@@ -171,6 +121,11 @@ function clearing(dir: string): string {
171
121
  return `clear it with \`chmod -R u+w ${dir} && rm -rf ${dir}\` and rerun ${NAME}`;
172
122
  }
173
123
 
124
+ function writableAt(dir: string, paths: readonly string[], by: string): string {
125
+ const [first = dir] = paths;
126
+ return `${paths.length === 1 ? "1 path" : `${paths.length} paths`} writable by ${by}, starting with ${first}`;
127
+ }
128
+
174
129
  const freeze = Effect.fn("freeze")(function* (dir: string) {
175
130
  const fs = yield* FileSystem.FileSystem;
176
131
  for (const { entry, mode } of yield* modes(dir)) {
@@ -212,7 +167,8 @@ const recorded = Effect.fn("recorded")(function* (dir: string) {
212
167
  return Option.some(text.trim());
213
168
  });
214
169
 
215
- const verify = Effect.fn("verify")(function* (dir: string, library: Library, installed: string, tag: string) {
170
+ // Returns the paths still carrying the owner write bit and fails on every other finding, reading the status only once none are left.
171
+ const inspect = Effect.fn("inspect")(function* (dir: string, library: Library, installed: string, tag: string) {
216
172
  const held = yield* recorded(dir);
217
173
  if (Option.isNone(held)) {
218
174
  return yield* new VendorError({ message: `${recordOf(dir)} is missing, so ${dir} has no commit to hold to; ${clearing(dir)}` });
@@ -223,13 +179,13 @@ const verify = Effect.fn("verify")(function* (dir: string, library: Library, ins
223
179
  return yield* new VendorError({ message: `${dir} sits on ${head}, not the recorded ${record}; ${clearing(dir)}` });
224
180
  }
225
181
  yield* checkVersion(dir, library, installed, tag);
226
- const tampered = (yield* modes(dir)).filter(({ mode }) => (mode & WRITE_BITS) !== 0).map(({ entry }) => entry);
227
- if (tampered.length > 0) {
228
- const [first = dir] = tampered;
229
- return yield* new VendorError({
230
- message: `${dir} leaves ${tampered.length} paths writable, starting with ${first}; ${clearing(dir)}`,
231
- });
182
+ const found = yield* modes(dir);
183
+ const shared = found.filter(({ mode }) => (mode & WRITE_BITS & ~OWNER_WRITE) !== 0).map(({ entry }) => entry);
184
+ if (shared.length > 0) {
185
+ return yield* new VendorError({ message: `${dir} leaves ${writableAt(dir, shared, "group or others")}; ${clearing(dir)}` });
232
186
  }
187
+ const writable = found.filter(({ mode }) => (mode & OWNER_WRITE) !== 0).map(({ entry }) => entry);
188
+ if (writable.length > 0) return writable;
233
189
  const status = yield* git(["status", "--porcelain", "--ignored"], dir).pipe(
234
190
  Effect.mapError((cause) => new VendorError({ message: `${dir} reports no status: ${cause.message}` })),
235
191
  );
@@ -237,6 +193,7 @@ const verify = Effect.fn("verify")(function* (dir: string, library: Library, ins
237
193
  const [first = ""] = status.trim().split("\n");
238
194
  return yield* new VendorError({ message: `${dir} holds writes outside the recorded commit, starting with ${first}; ${clearing(dir)}` });
239
195
  }
196
+ return writable;
240
197
  });
241
198
 
242
199
  const ensureLink = Effect.fn("ensureLink")(function* (root: string, library: Library, dir: string) {
@@ -315,8 +272,26 @@ const land = Effect.fn("land")(function* (library: Library, installed: string, t
315
272
  return yield* stage(staging, library, installed, tag, dir).pipe(Effect.ensuring(discard(staging)));
316
273
  });
317
274
 
318
- const vend = Effect.fn("vend")(function* (root: string, cache: string, library: Library) {
275
+ function verified(library: Library, tag: string): string {
276
+ return `${LINKS}/${library.name} still holds ${tag}, verified against its recorded commit`;
277
+ }
278
+
279
+ const settle = Effect.fn("settle")(function* (library: Library, installed: string, tag: string, dir: string) {
319
280
  const fs = yield* FileSystem.FileSystem;
281
+ if (!(yield* fs.exists(dir)) && (yield* land(library, installed, tag, dir))) {
282
+ return `cloned ${tag} from ${library.repository} and linked ${LINKS}/${library.name}`;
283
+ }
284
+ const writable = yield* inspect(dir, library, installed, tag);
285
+ if (writable.length === 0) return verified(library, tag);
286
+ yield* freeze(dir);
287
+ const left = yield* inspect(dir, library, installed, tag);
288
+ if (left.length > 0) {
289
+ return yield* new VendorError({ message: `${dir} leaves ${writableAt(dir, left, "its owner")}; ${clearing(dir)}` });
290
+ }
291
+ return `found ${writableAt(dir, writable, "its owner")}, and froze the tree again, so ${verified(library, tag)}`;
292
+ });
293
+
294
+ const vend = Effect.fn("vend")(function* (root: string, cache: string, library: Library) {
320
295
  const path = yield* Path.Path;
321
296
  const installed = yield* manifestVersion(
322
297
  path.join(root, "node_modules", ...library.package.split("/"), "package.json"),
@@ -324,12 +299,7 @@ const vend = Effect.fn("vend")(function* (root: string, cache: string, library:
324
299
  );
325
300
  const tag = tagFor(library.tag, installed);
326
301
  const dir = path.join(cache, LINKS, ...remoteSegments(library.repository), tag);
327
- if (!(yield* fs.exists(dir)) && (yield* land(library, installed, tag, dir))) {
328
- yield* Console.error(`${NAME}: cloned ${tag} from ${library.repository} and linked ${LINKS}/${library.name}`);
329
- } else {
330
- yield* verify(dir, library, installed, tag);
331
- yield* Console.error(`${NAME}: ${LINKS}/${library.name} still holds ${tag}, verified against its recorded commit`);
332
- }
302
+ yield* Console.error(`${NAME}: ${yield* settle(library, installed, tag, dir)}`);
333
303
  yield* ensureLink(root, library, dir);
334
304
  });
335
305