@stigmer/cli 3.16.0 → 3.16.1-dev.20260917224426

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.
Files changed (171) hide show
  1. package/commands/agent-exec-flags.d.ts +2 -5
  2. package/commands/agent-exec-flags.d.ts.map +1 -1
  3. package/commands/agent-exec-flags.js +6 -13
  4. package/commands/agent-exec-flags.js.map +1 -1
  5. package/commands/connect.js +1 -1
  6. package/commands/connect.js.map +1 -1
  7. package/commands/install.d.ts +3 -0
  8. package/commands/install.d.ts.map +1 -0
  9. package/commands/install.js +68 -0
  10. package/commands/install.js.map +1 -0
  11. package/commands/marketplace.d.ts +3 -0
  12. package/commands/marketplace.d.ts.map +1 -0
  13. package/commands/marketplace.js +94 -0
  14. package/commands/marketplace.js.map +1 -0
  15. package/commands/push.js +2 -7
  16. package/commands/push.js.map +1 -1
  17. package/commands/run.js +0 -3
  18. package/commands/run.js.map +1 -1
  19. package/config/config.d.ts +19 -0
  20. package/config/config.d.ts.map +1 -1
  21. package/config/config.js +15 -0
  22. package/config/config.js.map +1 -1
  23. package/config/index.d.ts +1 -1
  24. package/config/index.d.ts.map +1 -1
  25. package/config/index.js.map +1 -1
  26. package/local/bootstrap.d.ts +21 -0
  27. package/local/bootstrap.d.ts.map +1 -0
  28. package/local/bootstrap.js +88 -0
  29. package/local/bootstrap.js.map +1 -0
  30. package/local/daemon/launch.d.ts +2 -2
  31. package/local/daemon/launch.d.ts.map +1 -1
  32. package/local/daemon/launch.js +4 -34
  33. package/local/daemon/launch.js.map +1 -1
  34. package/local/daemon/process.d.ts +1 -1
  35. package/local/plugins/defaults.d.ts +35 -0
  36. package/local/plugins/defaults.d.ts.map +1 -0
  37. package/local/plugins/defaults.js +83 -0
  38. package/local/plugins/defaults.js.map +1 -0
  39. package/local/seedpack/content.d.ts +2 -1
  40. package/local/seedpack/content.d.ts.map +1 -1
  41. package/local/seedpack/content.js +5 -34
  42. package/local/seedpack/content.js.map +1 -1
  43. package/marketplace/config.d.ts +59 -0
  44. package/marketplace/config.d.ts.map +1 -0
  45. package/marketplace/config.js +162 -0
  46. package/marketplace/config.js.map +1 -0
  47. package/marketplace/github.d.ts +31 -0
  48. package/marketplace/github.d.ts.map +1 -0
  49. package/marketplace/github.js +203 -0
  50. package/marketplace/github.js.map +1 -0
  51. package/marketplace/index.d.ts +8 -0
  52. package/marketplace/index.d.ts.map +1 -0
  53. package/marketplace/index.js +12 -0
  54. package/marketplace/index.js.map +1 -0
  55. package/marketplace/install.d.ts +28 -0
  56. package/marketplace/install.d.ts.map +1 -0
  57. package/marketplace/install.js +109 -0
  58. package/marketplace/install.js.map +1 -0
  59. package/marketplace/materialize.d.ts +19 -0
  60. package/marketplace/materialize.d.ts.map +1 -0
  61. package/marketplace/materialize.js +53 -0
  62. package/marketplace/materialize.js.map +1 -0
  63. package/marketplace/official.d.ts +28 -0
  64. package/marketplace/official.d.ts.map +1 -0
  65. package/marketplace/official.js +86 -0
  66. package/marketplace/official.js.map +1 -0
  67. package/marketplace/read.d.ts +17 -0
  68. package/marketplace/read.d.ts.map +1 -0
  69. package/marketplace/read.js +44 -0
  70. package/marketplace/read.js.map +1 -0
  71. package/marketplace/ref.d.ts +13 -0
  72. package/marketplace/ref.d.ts.map +1 -0
  73. package/marketplace/ref.js +54 -0
  74. package/marketplace/ref.js.map +1 -0
  75. package/marketplace/render.d.ts +22 -0
  76. package/marketplace/render.d.ts.map +1 -0
  77. package/marketplace/render.js +118 -0
  78. package/marketplace/render.js.map +1 -0
  79. package/output/ndjson.js +1 -1
  80. package/output/ndjson.js.map +1 -1
  81. package/package.json +7 -6
  82. package/program.d.ts.map +1 -1
  83. package/program.js +4 -2
  84. package/program.js.map +1 -1
  85. package/resources/ignore/index.d.ts +2 -4
  86. package/resources/ignore/index.d.ts.map +1 -1
  87. package/resources/ignore/index.js +5 -5
  88. package/resources/ignore/index.js.map +1 -1
  89. package/resources/ignore/matcher.d.ts +2 -35
  90. package/resources/ignore/matcher.d.ts.map +1 -1
  91. package/resources/ignore/matcher.js +25 -109
  92. package/resources/ignore/matcher.js.map +1 -1
  93. package/resources/plugin.d.ts +50 -18
  94. package/resources/plugin.d.ts.map +1 -1
  95. package/resources/plugin.js +73 -74
  96. package/resources/plugin.js.map +1 -1
  97. package/resources/run/agent-exec.js +1 -1
  98. package/resources/run/agent-exec.js.map +1 -1
  99. package/resources/run/prepare.d.ts +8 -19
  100. package/resources/run/prepare.d.ts.map +1 -1
  101. package/resources/run/prepare.js +4 -4
  102. package/resources/run/prepare.js.map +1 -1
  103. package/resources/skill.d.ts +2 -2
  104. package/resources/skill.d.ts.map +1 -1
  105. package/resources/skill.js +6 -10
  106. package/resources/skill.js.map +1 -1
  107. package/src/commands/agent-exec-flags.ts +6 -12
  108. package/src/commands/connect.ts +1 -1
  109. package/src/commands/install.test.ts +289 -0
  110. package/src/commands/install.ts +108 -0
  111. package/src/commands/marketplace.test.ts +200 -0
  112. package/src/commands/marketplace.ts +131 -0
  113. package/src/commands/push.ts +5 -10
  114. package/src/commands/run.ts +0 -3
  115. package/src/config/config.ts +37 -0
  116. package/src/config/index.ts +1 -0
  117. package/src/local/bootstrap.test.ts +71 -0
  118. package/src/local/bootstrap.ts +133 -0
  119. package/src/local/daemon/launch.ts +5 -40
  120. package/src/local/daemon/process.ts +1 -1
  121. package/src/local/plugins/defaults.test.ts +238 -0
  122. package/src/local/plugins/defaults.ts +135 -0
  123. package/src/local/seedpack/content.ts +11 -42
  124. package/src/marketplace/__fixtures__/cursor-marketplace.ts +62 -0
  125. package/src/marketplace/config.test.ts +245 -0
  126. package/src/marketplace/config.ts +236 -0
  127. package/src/marketplace/github.test.ts +318 -0
  128. package/src/marketplace/github.ts +296 -0
  129. package/src/marketplace/index.ts +54 -0
  130. package/src/marketplace/install.test.ts +194 -0
  131. package/src/marketplace/install.ts +159 -0
  132. package/src/marketplace/materialize.ts +80 -0
  133. package/src/marketplace/official.test.ts +107 -0
  134. package/src/marketplace/official.ts +125 -0
  135. package/src/marketplace/read.test.ts +94 -0
  136. package/src/marketplace/read.ts +76 -0
  137. package/src/marketplace/ref.test.ts +179 -0
  138. package/src/marketplace/ref.ts +74 -0
  139. package/src/marketplace/render.ts +154 -0
  140. package/src/output/ndjson.ts +1 -1
  141. package/src/program.test.ts +4 -13
  142. package/src/program.ts +4 -2
  143. package/src/resources/ignore/ignore.test.ts +23 -100
  144. package/src/resources/ignore/index.ts +11 -8
  145. package/src/resources/ignore/matcher.ts +33 -136
  146. package/src/resources/plugin.test.ts +35 -6
  147. package/src/resources/plugin.ts +132 -85
  148. package/src/resources/run/agent-exec.ts +1 -1
  149. package/src/resources/run/prepare.test.ts +4 -18
  150. package/src/resources/run/prepare.ts +12 -24
  151. package/src/resources/skill.ts +6 -10
  152. package/commands/draft.d.ts +0 -3
  153. package/commands/draft.d.ts.map +0 -1
  154. package/commands/draft.js +0 -115
  155. package/commands/draft.js.map +0 -1
  156. package/resources/ignore/defaults.d.ts +0 -2
  157. package/resources/ignore/defaults.d.ts.map +0 -1
  158. package/resources/ignore/defaults.js +0 -140
  159. package/resources/ignore/defaults.js.map +0 -1
  160. package/resources/ignore/match.d.ts +0 -3
  161. package/resources/ignore/match.d.ts.map +0 -1
  162. package/resources/ignore/match.js +0 -166
  163. package/resources/ignore/match.js.map +0 -1
  164. package/resources/ignore/pattern.d.ts +0 -13
  165. package/resources/ignore/pattern.d.ts.map +0 -1
  166. package/resources/ignore/pattern.js +0 -118
  167. package/resources/ignore/pattern.js.map +0 -1
  168. package/src/commands/draft.ts +0 -189
  169. package/src/resources/ignore/defaults.ts +0 -155
  170. package/src/resources/ignore/match.ts +0 -158
  171. package/src/resources/ignore/pattern.ts +0 -131
@@ -1,86 +1,17 @@
1
- // Table tests for the gitignore-compatible ignore engine: the filepath.Match
2
- // port, the Pattern matcher (scoping, dir-only, globs, negation), and the
3
- // composed Matcher with source precedence + last-match-wins.
1
+ // The CLI's ignore edge: `createMatcher` reads `.gitignore` and
2
+ // `.stigmerignore` from the directory and builds the shared engine's matcher
3
+ // from their text. The engine's own tables (patterns, precedence,
4
+ // last-match-wins) live with the engine in `@stigmer/plugin-package`; what is
5
+ // pinned here is the disk reading — which files, honouring `respectGitignore`,
6
+ // an absent file contributing nothing.
4
7
 
5
8
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
9
  import { tmpdir } from "node:os";
7
10
  import { join } from "node:path";
8
11
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
9
- import { matchName } from "./match.js";
10
12
  import { createMatcher } from "./matcher.js";
11
- import { MatchResult, parsePattern } from "./pattern.js";
12
13
 
13
- describe("matchName (filepath.Match port)", () => {
14
- const cases: [string, string, boolean][] = [
15
- ["*.go", "main.go", true],
16
- ["*.go", "main.rs", false],
17
- ["*", "anything", true],
18
- ["a?c", "abc", true],
19
- ["a?c", "ac", false],
20
- ["[abc]", "b", true],
21
- ["[abc]", "d", false],
22
- ["[a-c]", "b", true],
23
- ["[a-c]", "z", false],
24
- ["[^a]", "b", true],
25
- ["[^a]", "a", false],
26
- ["foo*", "foobar", true],
27
- ["*bar", "foobar", true],
28
- ["h[a-z]llo", "hello", true],
29
- ["node_modules", "node_modules", true],
30
- ["\\*.go", "*.go", true],
31
- // Malformed patterns: Go returns ErrBadPattern → callers treat as no match.
32
- ["[", "a", false],
33
- ["[a-", "a", false],
34
- ];
35
- for (const [pattern, name, expected] of cases) {
36
- it(`${pattern} ~ ${name} => ${expected}`, () => {
37
- expect(matchName(pattern, name)).toBe(expected);
38
- });
39
- }
40
- });
41
-
42
- describe("parsePattern / Pattern.match", () => {
43
- it("directory-only patterns only match directories (or ancestors)", () => {
44
- const p = parsePattern("node_modules/", []);
45
- expect(p.match(["node_modules"], true)).toBe(MatchResult.Exclude);
46
- expect(p.match(["node_modules"], false)).toBe(MatchResult.NoMatch);
47
- expect(p.match(["src", "node_modules"], true)).toBe(MatchResult.Exclude);
48
- });
49
-
50
- it("simple name patterns match at any depth", () => {
51
- const p = parsePattern("*.log", []);
52
- expect(p.match(["app.log"], false)).toBe(MatchResult.Exclude);
53
- expect(p.match(["sub", "app.log"], false)).toBe(MatchResult.Exclude);
54
- expect(p.match(["app.txt"], false)).toBe(MatchResult.NoMatch);
55
- });
56
-
57
- it("leading slash anchors to the domain root", () => {
58
- const p = parsePattern("/build", []);
59
- expect(p.match(["build"], true)).toBe(MatchResult.Exclude);
60
- expect(p.match(["src", "build"], true)).toBe(MatchResult.NoMatch);
61
- });
62
-
63
- it("** matches zero-to-many directories", () => {
64
- const p = parsePattern("**/foo", []);
65
- expect(p.match(["foo"], false)).toBe(MatchResult.Exclude);
66
- expect(p.match(["a", "b", "foo"], false)).toBe(MatchResult.Exclude);
67
- expect(p.match(["a", "bar"], false)).toBe(MatchResult.NoMatch);
68
- });
69
-
70
- it("negation patterns produce Include", () => {
71
- const p = parsePattern("!keep.txt", []);
72
- expect(p.inclusion).toBe(true);
73
- expect(p.match(["keep.txt"], false)).toBe(MatchResult.Include);
74
- });
75
-
76
- it("domain scopes matching below a subtree", () => {
77
- const p = parsePattern("*.txt", ["sub"]);
78
- expect(p.match(["sub", "a.txt"], false)).toBe(MatchResult.Exclude);
79
- expect(p.match(["a.txt"], false)).toBe(MatchResult.NoMatch);
80
- });
81
- });
82
-
83
- describe("createMatcher (source precedence, last-match-wins)", () => {
14
+ describe("createMatcher (the disk edge over the shared engine)", () => {
84
15
  let dir: string;
85
16
  beforeEach(() => {
86
17
  dir = mkdtempSync(join(tmpdir(), "ignore-test-"));
@@ -89,51 +20,43 @@ describe("createMatcher (source precedence, last-match-wins)", () => {
89
20
  rmSync(dir, { recursive: true, force: true });
90
21
  });
91
22
 
92
- it("applies security defaults including the .env.example negation", () => {
93
- const m = createMatcher({ rootDir: dir, respectGitignore: false, includeDefaults: true });
23
+ it("applies the security defaults with no ignore files present", () => {
24
+ const m = createMatcher({ rootDir: dir, respectGitignore: true, includeDefaults: true });
94
25
  expect(m.match(".env", false)).toBe(true);
95
26
  expect(m.match(".env.example", false)).toBe(false);
96
- expect(m.match("id_rsa", false)).toBe(true);
97
- expect(m.match("node_modules", true)).toBe(true);
98
27
  });
99
28
 
100
- it("honors .gitignore with negation (last match wins)", () => {
29
+ it("reads .gitignore from the root when respected, and not otherwise", () => {
101
30
  writeFileSync(join(dir, ".gitignore"), "*.draft\n!keep.draft\n");
102
- const m = createMatcher({ rootDir: dir, respectGitignore: true, includeDefaults: true });
103
- expect(m.match("notes.draft", false)).toBe(true);
104
- expect(m.match("keep.draft", false)).toBe(false);
105
- });
106
-
107
- it("ignores .gitignore when respectGitignore is false", () => {
108
- writeFileSync(join(dir, ".gitignore"), "*.draft\n");
109
- const m = createMatcher({ rootDir: dir, respectGitignore: false, includeDefaults: true });
110
- expect(m.match("notes.draft", false)).toBe(false);
31
+ const respected = createMatcher({ rootDir: dir, respectGitignore: true, includeDefaults: true });
32
+ expect(respected.match("notes.draft", false)).toBe(true);
33
+ expect(respected.match("keep.draft", false)).toBe(false);
34
+ const ignored = createMatcher({ rootDir: dir, respectGitignore: false, includeDefaults: true });
35
+ expect(ignored.match("notes.draft", false)).toBe(false);
111
36
  });
112
37
 
113
- it(".stigmerignore is always loaded and outranks .gitignore", () => {
38
+ it("always reads .stigmerignore, which outranks .gitignore", () => {
114
39
  writeFileSync(join(dir, ".gitignore"), "!shared.draft\n");
115
40
  writeFileSync(join(dir, ".stigmerignore"), "*.draft\n");
116
41
  const m = createMatcher({ rootDir: dir, respectGitignore: true, includeDefaults: true });
117
42
  expect(m.match("shared.draft", false)).toBe(true);
118
43
  });
119
44
 
120
- it("CLI extra ignore/include override file sources", () => {
121
- writeFileSync(join(dir, ".gitignore"), "*.draft\n");
45
+ it("passes the CLI flags through", () => {
122
46
  const m = createMatcher({
123
47
  rootDir: dir,
124
48
  respectGitignore: true,
125
49
  includeDefaults: true,
126
50
  extraIgnore: ["*.tmp"],
127
- extraInclude: ["important.draft"],
51
+ extraInclude: ["important.tmp"],
128
52
  });
129
53
  expect(m.match("scratch.tmp", false)).toBe(true);
130
- expect(m.match("important.draft", false)).toBe(false);
54
+ expect(m.match("important.tmp", false)).toBe(false);
131
55
  });
132
56
 
133
- it("reports the matching source and reason", () => {
134
- const m = createMatcher({ rootDir: dir, respectGitignore: false, includeDefaults: true });
135
- const reason = m.matchWithReason(".env", false);
136
- expect(reason.ignored).toBe(true);
137
- expect(reason.source).toBe("defaults");
57
+ it("refuses a root that is not a directory", () => {
58
+ expect(() => createMatcher({ rootDir: "", respectGitignore: true, includeDefaults: true })).toThrow(
59
+ "rootDir is required",
60
+ );
138
61
  });
139
62
  });
@@ -1,17 +1,20 @@
1
- // Public surface of the gitignore-compatible ignore engine.
1
+ // Public surface of the CLI's ignore edge. The engine is
2
+ // `@stigmer/plugin-package/client`; re-exported here so the CLI's callers keep
3
+ // one import path and the shared engine stays the only implementation.
2
4
 
3
- export { DEFAULT_PATTERNS } from "./defaults.js";
4
- export { matchName } from "./match.js";
5
5
  export {
6
- createMatcher,
7
- type MatcherOptions,
6
+ DEFAULT_PATTERNS,
8
7
  type MatchReason,
8
+ MatchResult,
9
9
  Matcher,
10
- Reason,
10
+ type Pattern,
11
11
  REASON_TEXT,
12
+ Reason,
12
13
  SOURCE_CLI,
13
14
  SOURCE_DEFAULTS,
14
15
  SOURCE_GITIGNORE,
15
16
  SOURCE_STIGMERIGNORE,
16
- } from "./matcher.js";
17
- export { MatchResult, type Pattern, parsePattern } from "./pattern.js";
17
+ matchName,
18
+ parsePattern,
19
+ } from "@stigmer/plugin-package/client";
20
+ export { createMatcher, type MatcherOptions } from "./matcher.js";
@@ -1,43 +1,21 @@
1
- // Ignore matcher: composes pattern sources with gitignore precedence (defaults
2
- // → .gitignore → .stigmerignore → CLI flags), evaluating "last match wins".
3
- // A faithful port of the Go CLI's pkg/ignore Matcher.
1
+ // The CLI's edge for the shared ignore engine: read the ignore files from the
2
+ // directory about to be packaged and build the matcher from their text.
3
+ //
4
+ // The engine itself (patterns, precedence, last-match-wins, the security
5
+ // defaults) lives in `@stigmer/plugin-package/client`, where the console
6
+ // builds the same matcher from a marketplace tree's file list. What is left
7
+ // here is what only a filesystem client needs: which files to read and that
8
+ // an absent or unreadable ignore file is normal and contributes nothing.
4
9
 
5
10
  import { readFileSync, statSync } from "node:fs";
6
11
  import { join } from "node:path";
7
- import { DEFAULT_PATTERNS } from "./defaults.js";
8
- import { MatchResult, type Pattern, parsePattern } from "./pattern.js";
9
-
10
- export const SOURCE_DEFAULTS = "defaults";
11
- export const SOURCE_GITIGNORE = ".gitignore";
12
- export const SOURCE_STIGMERIGNORE = ".stigmerignore";
13
- export const SOURCE_CLI = "cli";
14
-
15
- export enum Reason {
16
- NoMatch = 0,
17
- Excluded = 1,
18
- Included = 2,
19
- DefaultDeny = 3,
20
- }
21
-
22
- export const REASON_TEXT: Readonly<Record<Reason, string>> = {
23
- [Reason.NoMatch]: "no pattern matched",
24
- [Reason.Excluded]: "excluded by pattern",
25
- [Reason.Included]: "included by negation pattern",
26
- [Reason.DefaultDeny]: "excluded by security default",
27
- };
28
-
29
- export interface MatchReason {
30
- readonly ignored: boolean;
31
- readonly reason: Reason;
32
- readonly source: string;
33
- readonly pattern: string;
34
- }
35
-
36
- interface PatternEntry {
37
- readonly pattern: Pattern;
38
- readonly source: string;
39
- readonly raw: string;
40
- }
12
+ import {
13
+ type IgnoreSources,
14
+ type Matcher,
15
+ SOURCE_GITIGNORE,
16
+ SOURCE_STIGMERIGNORE,
17
+ buildMatcher,
18
+ } from "@stigmer/plugin-package/client";
41
19
 
42
20
  export interface MatcherOptions {
43
21
  readonly rootDir: string;
@@ -47,112 +25,31 @@ export interface MatcherOptions {
47
25
  readonly extraInclude?: readonly string[];
48
26
  }
49
27
 
50
- export class Matcher {
51
- private readonly entries: readonly PatternEntry[];
52
-
53
- constructor(entries: readonly PatternEntry[]) {
54
- this.entries = entries;
55
- }
56
-
57
- /** Decide whether a path (relative, forward-slash) is ignored, with reason. */
58
- matchWithReason(path: string, isDir: boolean): MatchReason {
59
- const components = pathToComponents(path);
60
- if (components.length === 0) {
61
- return { ignored: false, reason: Reason.NoMatch, source: "", pattern: "" };
62
- }
63
-
64
- let lastMatch: PatternEntry | undefined;
65
- let lastResult = MatchResult.NoMatch;
66
- for (const entry of this.entries) {
67
- const result = entry.pattern.match(components, isDir);
68
- if (result !== MatchResult.NoMatch) {
69
- lastMatch = entry;
70
- lastResult = result;
71
- }
72
- }
73
-
74
- if (lastMatch === undefined) {
75
- return { ignored: false, reason: Reason.NoMatch, source: "", pattern: "" };
76
- }
77
-
78
- if (lastResult === MatchResult.Exclude) {
79
- const reason = lastMatch.source === SOURCE_DEFAULTS ? Reason.DefaultDeny : Reason.Excluded;
80
- return { ignored: true, reason, source: lastMatch.source, pattern: lastMatch.raw };
81
- }
82
- // Include (negation): explicitly un-ignored.
83
- return { ignored: false, reason: Reason.Included, source: lastMatch.source, pattern: lastMatch.raw };
84
- }
85
-
86
- /** Whether the path should be excluded from the artifact. */
87
- match(path: string, isDir: boolean): boolean {
88
- return this.matchWithReason(path, isDir).ignored;
89
- }
90
-
91
- /** All loaded patterns as "[source] pattern" (for dry-run diagnostics). */
92
- patterns(): string[] {
93
- return this.entries.map((entry) => `[${entry.source}] ${entry.raw}`);
94
- }
95
- }
96
-
97
- /** Build a Matcher for `rootDir`, loading the configured pattern sources. */
28
+ /** Build a Matcher for `rootDir`, loading the configured pattern sources from disk. */
98
29
  export function createMatcher(options: MatcherOptions): Matcher {
99
30
  if (options.rootDir === "") throw new Error("rootDir is required");
100
31
  const stat = statSync(options.rootDir);
101
32
  if (!stat.isDirectory()) throw new Error(`rootDir is not a directory: ${options.rootDir}`);
102
33
 
103
- const entries: PatternEntry[] = [];
104
- if (options.includeDefaults) {
105
- entries.push(...parseEntries(DEFAULT_PATTERNS, SOURCE_DEFAULTS));
106
- }
107
- if (options.respectGitignore) {
108
- entries.push(...loadIgnoreFile(join(options.rootDir, ".gitignore"), SOURCE_GITIGNORE));
109
- }
110
- entries.push(...loadIgnoreFile(join(options.rootDir, ".stigmerignore"), SOURCE_STIGMERIGNORE));
111
-
112
- const extraIgnore = options.extraIgnore ?? [];
113
- const extraInclude = options.extraInclude ?? [];
114
- if (extraIgnore.length > 0 || extraInclude.length > 0) {
115
- entries.push(...loadCliPatterns(extraIgnore, extraInclude));
116
- }
117
-
118
- return new Matcher(entries);
119
- }
120
-
121
- function parseEntries(patterns: readonly string[], source: string): PatternEntry[] {
122
- const entries: PatternEntry[] = [];
123
- for (const raw of patterns) {
124
- const trimmed = raw.trim();
125
- if (trimmed === "" || trimmed.startsWith("#")) continue;
126
- entries.push({ pattern: parsePattern(trimmed, []), source, raw: trimmed });
127
- }
128
- return entries;
129
- }
130
-
131
- function loadIgnoreFile(path: string, source: string): PatternEntry[] {
132
- let text: string;
34
+ const gitignore = options.respectGitignore
35
+ ? readIgnoreFile(join(options.rootDir, SOURCE_GITIGNORE))
36
+ : undefined;
37
+ const stigmerignore = readIgnoreFile(join(options.rootDir, SOURCE_STIGMERIGNORE));
38
+ const sources: IgnoreSources = {
39
+ includeDefaults: options.includeDefaults,
40
+ ...(gitignore !== undefined && { gitignore }),
41
+ ...(stigmerignore !== undefined && { stigmerignore }),
42
+ ...(options.extraIgnore !== undefined && { extraIgnore: options.extraIgnore }),
43
+ ...(options.extraInclude !== undefined && { extraInclude: options.extraInclude }),
44
+ };
45
+ return buildMatcher(sources);
46
+ }
47
+
48
+ function readIgnoreFile(path: string): string | undefined {
133
49
  try {
134
- text = readFileSync(path, "utf8");
50
+ return readFileSync(path, "utf8");
135
51
  } catch {
136
52
  // Absent or unreadable ignore files are normal — no patterns.
137
- return [];
53
+ return undefined;
138
54
  }
139
- return parseEntries(text.split(/\r?\n/), source);
140
- }
141
-
142
- function loadCliPatterns(ignore: readonly string[], include: readonly string[]): PatternEntry[] {
143
- const entries = parseEntries(ignore, SOURCE_CLI);
144
- const negations: string[] = [];
145
- for (const pattern of include) {
146
- const trimmed = pattern.trim();
147
- if (trimmed === "") continue;
148
- negations.push(trimmed.startsWith("!") ? trimmed : `!${trimmed}`);
149
- }
150
- entries.push(...parseEntries(negations, SOURCE_CLI));
151
- return entries;
152
- }
153
-
154
- function pathToComponents(path: string): string[] {
155
- const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
156
- if (normalized === "" || normalized === ".") return [];
157
- return normalized.split("/");
158
55
  }
@@ -1,7 +1,9 @@
1
1
  // Pins the plugin directory walker: detection by any of the four manifests,
2
2
  // sorted sized entries, the ignore matcher applied (defaults, .gitignore,
3
3
  // .stigmerignore), symlinks never followed, and the JSON projection that
4
- // reduces overlay documents to their paths.
4
+ // reduces overlay documents to their paths; then the prepared push (the
5
+ // deterministic archive and its SHA-256, the server's own digest) and the
6
+ // install summary renderer.
5
7
 
6
8
  import {
7
9
  mkdirSync,
@@ -132,13 +134,13 @@ describe("readPluginDirectory", () => {
132
134
  });
133
135
  });
134
136
 
135
- describe("zipPluginFiles", () => {
136
- it("writes the walked list deterministically and round-trips through the library's own reader", async () => {
137
- const { zipPluginFiles } = await import("./plugin.js");
137
+ describe("the archive of a walked directory", () => {
138
+ it("is deterministic and round-trips through the library's own reader", async () => {
139
+ const { archivePlugin } = await import("@stigmer/plugin-package/client");
138
140
  const { unzipSync } = await import("fflate");
139
141
  const directory = readPluginDirectory(root);
140
- const first = zipPluginFiles(directory.files);
141
- const second = zipPluginFiles(directory.files);
142
+ const first = archivePlugin(directory.files);
143
+ const second = archivePlugin(directory.files);
142
144
  expect(Buffer.from(first).equals(Buffer.from(second))).toBe(true);
143
145
  const entries = Object.keys(unzipSync(first)).sort();
144
146
  expect(entries).toEqual(directory.files.entries.map((e) => e.path));
@@ -147,6 +149,33 @@ describe("zipPluginFiles", () => {
147
149
  });
148
150
  });
149
151
 
152
+ describe("preparePluginPush", () => {
153
+ it("carries the walked archive and its SHA-256, the identity the server records", async () => {
154
+ const { createHash } = await import("node:crypto");
155
+ const { preparePluginPush } = await import("./plugin.js");
156
+ const { archivePlugin } = await import("@stigmer/plugin-package/client");
157
+ const prepared = await preparePluginPush(root);
158
+ expect(prepared.plugin.name).toBe("walker");
159
+ expect(prepared.dir).toBe(root);
160
+ expect(prepared.stats.filesIgnored).toBe(3);
161
+ const bytes = archivePlugin(readPluginDirectory(root).files);
162
+ expect(Buffer.from(prepared.archive).equals(Buffer.from(bytes))).toBe(true);
163
+ expect(prepared.digest).toBe(createHash("sha256").update(bytes).digest("hex"));
164
+ expect(prepared.digest).toMatch(/^[a-f0-9]{64}$/);
165
+ });
166
+
167
+ it("refuses offline with the same sentences validate prints", async () => {
168
+ const { preparePluginPush } = await import("./plugin.js");
169
+ const broken = mkdtempSync(join(tmpdir(), "stigmer-plugin-broken-"));
170
+ try {
171
+ writeFileSync(join(broken, "plugin.json"), JSON.stringify({ name: "Not Valid!" }));
172
+ await expect(preparePluginPush(broken)).rejects.toThrow(/plugin cannot be installed/);
173
+ } finally {
174
+ rmSync(broken, { recursive: true, force: true });
175
+ }
176
+ });
177
+ });
178
+
150
179
  describe("renderPushOutcome", () => {
151
180
  it("summarises the install by kind, lists members and carries warnings", async () => {
152
181
  const { renderPushOutcome } = await import("./plugin.js");