@stigmer/cli 3.15.3-dev.20260916211208 → 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.
- package/commands/agent-exec-flags.d.ts +2 -5
- package/commands/agent-exec-flags.d.ts.map +1 -1
- package/commands/agent-exec-flags.js +6 -13
- package/commands/agent-exec-flags.js.map +1 -1
- package/commands/connect.js +1 -1
- package/commands/connect.js.map +1 -1
- package/commands/install.d.ts +3 -0
- package/commands/install.d.ts.map +1 -0
- package/commands/install.js +68 -0
- package/commands/install.js.map +1 -0
- package/commands/marketplace.d.ts +3 -0
- package/commands/marketplace.d.ts.map +1 -0
- package/commands/marketplace.js +94 -0
- package/commands/marketplace.js.map +1 -0
- package/commands/push.js +2 -7
- package/commands/push.js.map +1 -1
- package/commands/run.js +0 -3
- package/commands/run.js.map +1 -1
- package/config/config.d.ts +19 -0
- package/config/config.d.ts.map +1 -1
- package/config/config.js +15 -0
- package/config/config.js.map +1 -1
- package/config/index.d.ts +1 -1
- package/config/index.d.ts.map +1 -1
- package/config/index.js.map +1 -1
- package/local/bootstrap.d.ts +21 -0
- package/local/bootstrap.d.ts.map +1 -0
- package/local/bootstrap.js +88 -0
- package/local/bootstrap.js.map +1 -0
- package/local/daemon/launch.d.ts +2 -2
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/daemon/launch.js +4 -34
- package/local/daemon/launch.js.map +1 -1
- package/local/daemon/process.d.ts +1 -1
- package/local/plugins/defaults.d.ts +35 -0
- package/local/plugins/defaults.d.ts.map +1 -0
- package/local/plugins/defaults.js +83 -0
- package/local/plugins/defaults.js.map +1 -0
- package/local/seedpack/content.d.ts +2 -1
- package/local/seedpack/content.d.ts.map +1 -1
- package/local/seedpack/content.js +5 -34
- package/local/seedpack/content.js.map +1 -1
- package/marketplace/config.d.ts +59 -0
- package/marketplace/config.d.ts.map +1 -0
- package/marketplace/config.js +162 -0
- package/marketplace/config.js.map +1 -0
- package/marketplace/github.d.ts +31 -0
- package/marketplace/github.d.ts.map +1 -0
- package/marketplace/github.js +203 -0
- package/marketplace/github.js.map +1 -0
- package/marketplace/index.d.ts +8 -0
- package/marketplace/index.d.ts.map +1 -0
- package/marketplace/index.js +12 -0
- package/marketplace/index.js.map +1 -0
- package/marketplace/install.d.ts +28 -0
- package/marketplace/install.d.ts.map +1 -0
- package/marketplace/install.js +109 -0
- package/marketplace/install.js.map +1 -0
- package/marketplace/materialize.d.ts +19 -0
- package/marketplace/materialize.d.ts.map +1 -0
- package/marketplace/materialize.js +53 -0
- package/marketplace/materialize.js.map +1 -0
- package/marketplace/official.d.ts +28 -0
- package/marketplace/official.d.ts.map +1 -0
- package/marketplace/official.js +86 -0
- package/marketplace/official.js.map +1 -0
- package/marketplace/read.d.ts +17 -0
- package/marketplace/read.d.ts.map +1 -0
- package/marketplace/read.js +44 -0
- package/marketplace/read.js.map +1 -0
- package/marketplace/ref.d.ts +13 -0
- package/marketplace/ref.d.ts.map +1 -0
- package/marketplace/ref.js +54 -0
- package/marketplace/ref.js.map +1 -0
- package/marketplace/render.d.ts +22 -0
- package/marketplace/render.d.ts.map +1 -0
- package/marketplace/render.js +118 -0
- package/marketplace/render.js.map +1 -0
- package/output/ndjson.js +1 -1
- package/output/ndjson.js.map +1 -1
- package/package.json +7 -6
- package/program.d.ts.map +1 -1
- package/program.js +4 -2
- package/program.js.map +1 -1
- package/resources/ignore/index.d.ts +2 -4
- package/resources/ignore/index.d.ts.map +1 -1
- package/resources/ignore/index.js +5 -5
- package/resources/ignore/index.js.map +1 -1
- package/resources/ignore/matcher.d.ts +2 -35
- package/resources/ignore/matcher.d.ts.map +1 -1
- package/resources/ignore/matcher.js +25 -109
- package/resources/ignore/matcher.js.map +1 -1
- package/resources/plugin.d.ts +50 -18
- package/resources/plugin.d.ts.map +1 -1
- package/resources/plugin.js +73 -74
- package/resources/plugin.js.map +1 -1
- package/resources/run/agent-exec.js +1 -1
- package/resources/run/agent-exec.js.map +1 -1
- package/resources/run/prepare.d.ts +8 -19
- package/resources/run/prepare.d.ts.map +1 -1
- package/resources/run/prepare.js +4 -4
- package/resources/run/prepare.js.map +1 -1
- package/resources/skill.d.ts +2 -2
- package/resources/skill.d.ts.map +1 -1
- package/resources/skill.js +6 -10
- package/resources/skill.js.map +1 -1
- package/src/commands/agent-exec-flags.ts +6 -12
- package/src/commands/connect.ts +1 -1
- package/src/commands/install.test.ts +289 -0
- package/src/commands/install.ts +108 -0
- package/src/commands/marketplace.test.ts +200 -0
- package/src/commands/marketplace.ts +131 -0
- package/src/commands/push.ts +5 -10
- package/src/commands/run.ts +0 -3
- package/src/config/config.ts +37 -0
- package/src/config/index.ts +1 -0
- package/src/local/bootstrap.test.ts +71 -0
- package/src/local/bootstrap.ts +133 -0
- package/src/local/daemon/launch.ts +5 -40
- package/src/local/daemon/process.ts +1 -1
- package/src/local/plugins/defaults.test.ts +238 -0
- package/src/local/plugins/defaults.ts +135 -0
- package/src/local/seedpack/content.ts +11 -42
- package/src/marketplace/__fixtures__/cursor-marketplace.ts +62 -0
- package/src/marketplace/config.test.ts +245 -0
- package/src/marketplace/config.ts +236 -0
- package/src/marketplace/github.test.ts +318 -0
- package/src/marketplace/github.ts +296 -0
- package/src/marketplace/index.ts +54 -0
- package/src/marketplace/install.test.ts +194 -0
- package/src/marketplace/install.ts +159 -0
- package/src/marketplace/materialize.ts +80 -0
- package/src/marketplace/official.test.ts +107 -0
- package/src/marketplace/official.ts +125 -0
- package/src/marketplace/read.test.ts +94 -0
- package/src/marketplace/read.ts +76 -0
- package/src/marketplace/ref.test.ts +179 -0
- package/src/marketplace/ref.ts +74 -0
- package/src/marketplace/render.ts +154 -0
- package/src/output/ndjson.ts +1 -1
- package/src/program.test.ts +4 -13
- package/src/program.ts +4 -2
- package/src/resources/ignore/ignore.test.ts +23 -100
- package/src/resources/ignore/index.ts +11 -8
- package/src/resources/ignore/matcher.ts +33 -136
- package/src/resources/plugin.test.ts +35 -6
- package/src/resources/plugin.ts +132 -85
- package/src/resources/run/agent-exec.ts +1 -1
- package/src/resources/run/prepare.test.ts +4 -18
- package/src/resources/run/prepare.ts +12 -24
- package/src/resources/skill.ts +6 -10
- package/commands/draft.d.ts +0 -3
- package/commands/draft.d.ts.map +0 -1
- package/commands/draft.js +0 -115
- package/commands/draft.js.map +0 -1
- package/resources/ignore/defaults.d.ts +0 -2
- package/resources/ignore/defaults.d.ts.map +0 -1
- package/resources/ignore/defaults.js +0 -140
- package/resources/ignore/defaults.js.map +0 -1
- package/resources/ignore/match.d.ts +0 -3
- package/resources/ignore/match.d.ts.map +0 -1
- package/resources/ignore/match.js +0 -166
- package/resources/ignore/match.js.map +0 -1
- package/resources/ignore/pattern.d.ts +0 -13
- package/resources/ignore/pattern.d.ts.map +0 -1
- package/resources/ignore/pattern.js +0 -118
- package/resources/ignore/pattern.js.map +0 -1
- package/src/commands/draft.ts +0 -189
- package/src/resources/ignore/defaults.ts +0 -155
- package/src/resources/ignore/match.ts +0 -158
- package/src/resources/ignore/pattern.ts +0 -131
|
@@ -1,86 +1,17 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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("
|
|
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
|
|
93
|
-
const m = createMatcher({ rootDir: dir, respectGitignore:
|
|
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("
|
|
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
|
|
103
|
-
expect(
|
|
104
|
-
expect(
|
|
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
|
|
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("
|
|
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.
|
|
51
|
+
extraInclude: ["important.tmp"],
|
|
128
52
|
});
|
|
129
53
|
expect(m.match("scratch.tmp", false)).toBe(true);
|
|
130
|
-
expect(m.match("important.
|
|
54
|
+
expect(m.match("important.tmp", false)).toBe(false);
|
|
131
55
|
});
|
|
132
56
|
|
|
133
|
-
it("
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
type MatcherOptions,
|
|
6
|
+
DEFAULT_PATTERNS,
|
|
8
7
|
type MatchReason,
|
|
8
|
+
MatchResult,
|
|
9
9
|
Matcher,
|
|
10
|
-
|
|
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
|
-
|
|
17
|
-
|
|
17
|
+
matchName,
|
|
18
|
+
parsePattern,
|
|
19
|
+
} from "@stigmer/plugin-package/client";
|
|
20
|
+
export { createMatcher, type MatcherOptions } from "./matcher.js";
|
|
@@ -1,43 +1,21 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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("
|
|
136
|
-
it("
|
|
137
|
-
const {
|
|
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 =
|
|
141
|
-
const second =
|
|
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");
|