@wildwinter/toolkit 0.2.1 → 0.2.3

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/dist/vcs.cjs ADDED
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+
3
+ var child_process = require('child_process');
4
+
5
+ // src/vcs.ts
6
+ var VCS_KINDS = ["git", "perforce", "plastic", "svn"];
7
+ var isVcsKind = (value) => typeof value === "string" && VCS_KINDS.includes(value);
8
+ var VCS_LABELS = {
9
+ git: "Git",
10
+ perforce: "Perforce",
11
+ plastic: "Plastic SCM",
12
+ svn: "Subversion (SVN)"
13
+ };
14
+ var VCS_SETUP_FILE = "vcs-setup.md";
15
+ function ignoreFileFor(vcs) {
16
+ switch (vcs) {
17
+ case "git":
18
+ return ".gitignore";
19
+ case "perforce":
20
+ return ".p4ignore";
21
+ case "plastic":
22
+ return "ignore.conf";
23
+ default:
24
+ return void 0;
25
+ }
26
+ }
27
+ function vcsConfigFiles(format, vcs, bundle = "commit") {
28
+ const files = [{ path: VCS_SETUP_FILE, content: vcsSetup(format, vcs, bundle) }];
29
+ if (vcs === "git") files.push({ path: ".gitattributes", content: gitAttributes(format) });
30
+ const ignore = ignoreFileFor(vcs);
31
+ if (ignore !== void 0) files.push({ path: ignore, content: ignoreContent(format, bundle) });
32
+ return files;
33
+ }
34
+ function bundlePostureOf(format, vcs, read) {
35
+ const ignore = ignoreFileFor(vcs);
36
+ if (ignore !== void 0 && (read(ignore) ?? "").split(/\r?\n/).some((l) => l.trim() === `*.${format.bundle}`)) return "ignore";
37
+ return (read(VCS_SETUP_FILE) ?? "").includes(IGNORES_MARK) ? "ignore" : "commit";
38
+ }
39
+ var IGNORES_MARK = "This project IGNORES the compiled";
40
+ var column = (patterns) => Math.max(...patterns.map((p) => p.length)) + 1;
41
+ function gitAttributes(f) {
42
+ const source = f.source.map((ext) => `*.${ext}`);
43
+ const manifest = f.manifest !== void 0 ? `${f.manifest.dir}/*${f.manifest.suffix}` : void 0;
44
+ const w = column([...source, `*.${f.bundle}`, `*.${f.pack}`]);
45
+ const row = (pattern, attrs) => `${pattern.padEnd(w)}${pattern.length >= w ? " " : ""}${attrs}`;
46
+ const blocks = [
47
+ `# ${f.product} source is UTF-8 + LF text (pinned; never let autocrlf touch it).
48
+ ` + source.map((p) => row(p, "text eol=lf")).join("\n"),
49
+ `# Id-keyed structured merge for ${f.product} source (the '${f.tool} merge' driver).
50
+ # Register it once per clone (see ${VCS_SETUP_FILE}); until then git falls back to a
51
+ # normal text merge for these: no harm, just not structure-aware.
52
+ ` + source.map((p) => row(p, `merge=${f.driver}`)).join("\n")
53
+ ];
54
+ if (manifest !== void 0) {
55
+ blocks.push(`# An extension's manifest is the extension's own JSON, kept in the project so
56
+ # every machine reads the same one, and never edited here: text, merged as text.
57
+ ` + row(manifest, "text eol=lf"));
58
+ }
59
+ blocks.push(`# Generated artifacts. The compiled bundle is committed but REGENERATED, never
60
+ # hand-merged: keep ours on conflict and rebuild ('${f.tool} validate' catches a
61
+ # stale one). Needs a one-time 'git config merge.ours.driver true' (see ${VCS_SETUP_FILE}).
62
+ ` + row(`*.${f.bundle}`, "text eol=lf merge=ours") + `
63
+ # The packed document is a binary zip envelope, kept out of version control (.gitignore).
64
+ ` + row(`*.${f.pack}`, "binary"));
65
+ for (const b of f.binary ?? []) blocks.push(`${b.note.split("\n").map((l) => `# ${l}`).join("\n")}
66
+ ${row(b.pattern, "binary")}`);
67
+ return blocks.join("\n\n") + "\n";
68
+ }
69
+ function ignoreContent(f, bundle) {
70
+ const lines = [
71
+ `# ${f.product} generated artifacts: not source (see ${VCS_SETUP_FILE}).`,
72
+ "# Packed document (the send-and-return envelope):",
73
+ `*.${f.pack}`,
74
+ "# Unresolved merge sidecar (a lingering one is a validate error). The merge also names its clashes",
75
+ '# in the shard itself, under "unresolved", which IS committed with the file, so CI refuses an',
76
+ "# unresolved merge wherever it goes:",
77
+ `*.${f.conflict}`
78
+ ];
79
+ if (bundle === "ignore") lines.push("# Compiled bundle (built in CI, not committed under this posture):", `*.${f.bundle}`);
80
+ return lines.join("\n") + "\n";
81
+ }
82
+ function vcsSetup(f, vcs, bundle) {
83
+ const text = [...f.source, ...f.manifest !== void 0 ? [f.manifest.suffix.replace(/^\./, "")] : [], f.bundle];
84
+ const sections = {
85
+ git: `## git
86
+
87
+ \`.gitattributes\` (already emitted) pins ${f.product} source shards to UTF-8 + LF
88
+ text and marks the generated artifacts (the \`.${f.bundle}\` bundle \`merge=ours\`, the
89
+ \`.${f.pack}\` document \`binary\`). \`.gitignore\` (already emitted) keeps the document
90
+ (and the bundle, under the "ignore" posture) out of source control.
91
+
92
+ Recommended pre-commit hook (\`.git/hooks/pre-commit\`, executable):
93
+
94
+ #!/bin/sh
95
+ ${f.tool} validate || exit 1
96
+
97
+ The \`merge=${f.driver}\` rules in \`.gitattributes\` are already active; register the
98
+ drivers once per clone (git config is not repo-tracked). Until then git falls
99
+ back to a normal text merge for those files:
100
+
101
+ git config merge.${f.driver}.name "${f.product} structured merge"
102
+ git config merge.${f.driver}.driver "${f.tool} merge %O %A %B -o %A --path %P"
103
+ git config merge.ours.driver true
104
+
105
+ git calls the per-path driver directly (no \`mergetool\` wrapper needed). \`%O %A %B\`
106
+ are base, ours and theirs; the merged result is written back to \`%A\`, and \`%P\` is
107
+ the file's real path. On a conflict \`${f.tool} merge\` exits non-zero and writes a
108
+ \`.${f.conflict}\` sidecar beside that file, so the merge stays unresolved.
109
+ `,
110
+ perforce: `## Perforce
111
+
112
+ Add ${f.product}'s extensions to the typemap (\`p4 typemap\`): source shards and
113
+ the compiled bundle are TEXT with LF; the packed document is BINARY:
114
+
115
+ ${text.map((ext) => ` text //....${ext}`).join("\n")}
116
+ binary //....${f.pack}
117
+
118
+ On a unicode-mode server, set \`P4CHARSET=utf8\`. For a lock-based workflow (one
119
+ lock per file), add \`+l\` to make checkouts exclusive. A \`.p4ignore\` (already
120
+ emitted) keeps the packed document out of the depot.
121
+
122
+ Perforce allows ONE global merge tool, so set \`${f.tool} mergetool\` as it: it
123
+ runs the structured merge for ${f.product} source and hands everything else to
124
+ your normal tool. Map Perforce's variables into the order BASE THEIRS OURS OUT:
125
+
126
+ ${f.tool} mergetool --fallback "<your merge tool>" %b %t %y %r
127
+
128
+ (\`%b\` base, \`%t\` theirs, \`%y\` yours, \`%r\` result: adjust to your P4 client's
129
+ variable names; the argument ORDER is what matters.)
130
+ `,
131
+ plastic: `## Plastic SCM (Unity Version Control)
132
+
133
+ ${f.product} source shards are plain UTF-8 text; Plastic handles them as they
134
+ are. The \`ignore.conf\` (already emitted) keeps the packed document out of the
135
+ repository. For a lock-based workflow, configure exclusive checkout (lock.conf)
136
+ for the shard extensions.
137
+
138
+ In Preferences > Merge tools, add an external tool (a global entry is fine: the
139
+ wrapper looks at each file's name) with the arguments in BASE THEIRS OURS OUT
140
+ order:
141
+
142
+ ${f.tool} mergetool --fallback "<your merge tool>" @basefile @sourcefile @destinationfile @output
143
+ `,
144
+ svn: `## SVN
145
+
146
+ Set auto-props so ${f.product} source keeps LF (\`~/.subversion/config\` or the
147
+ repository's config):
148
+
149
+ [auto-props]
150
+ ${text.map((ext) => ` *.${ext} = svn:eol-style=LF`).join("\n")}
151
+ *.${f.pack} = svn:mime-type=application/octet-stream
152
+
153
+ SVN's ignore is a folder PROPERTY, not a file; set it from the project folder:
154
+
155
+ svn propset svn:ignore "*.${f.pack}${bundle === "ignore" ? `\\n*.${f.bundle}` : ""}\\n*.${f.conflict}" .
156
+
157
+ For a lock-based workflow, add \`svn:needs-lock\` to the shard patterns.
158
+
159
+ SVN allows one global merge tool. Point \`[helpers] merge-tool-cmd\` at a small
160
+ wrapper script that passes SVN's four arguments (base, theirs, mine, merged: BASE
161
+ THEIRS OURS OUT) to \`${f.tool} mergetool\`:
162
+
163
+ #!/bin/sh
164
+ exec ${f.tool} mergetool --fallback "<your merge tool>" "$1" "$2" "$3" "$4"
165
+ `
166
+ };
167
+ const head = vcs !== void 0 ? sections[vcs] : `No version control system chosen (\`${f.tool} init --vcs ${VCS_KINDS.join("|")}\` writes
168
+ the setup for one). Whichever you adopt, the rules are the same: ${f.product} source
169
+ is UTF-8 + LF text, and \`${f.tool} validate\` belongs in your pre-commit hook and
170
+ CI. Sections for every supported system:
171
+
172
+ ${VCS_KINDS.map((k) => sections[k]).join("\n")}`;
173
+ return `# Version-control setup for this ${f.product} project
174
+
175
+ ${head}
176
+ ${bundleSection(f, bundle)}`;
177
+ }
178
+ function bundleSection(f, bundle) {
179
+ const posture = bundle === "commit" ? `This project COMMITS the compiled \`.${f.bundle}\` bundle (\`${f.tool} init --bundle ignore\`
180
+ builds it in CI instead). The bundle is regenerated, never hand-merged: on a
181
+ conflict, keep ours and run \`${f.tool} export\` again. \`${f.tool} validate\` works out
182
+ the bundle's hash from source and FAILS if it is stale, so a forgotten rebuild
183
+ cannot ship unnoticed.` : `${IGNORES_MARK} \`.${f.bundle}\` bundle (built in CI; \`${f.tool} init
184
+ --bundle commit\` commits it instead). Run \`${f.tool} export\` as a build step.`;
185
+ return `## Compiled bundle and packed document
186
+
187
+ ${posture}
188
+
189
+ The packed \`.${f.pack}\` document (\`${f.tool} pack\`) is the send-and-return envelope
190
+ for collaborators without version control. It is a binary zip and a projection of
191
+ the shards, NOT source, so it stays out of version control (ignored above); edits
192
+ come back with \`${f.tool} unpack\`.
193
+ `;
194
+ }
195
+ function runMergetool(o) {
196
+ if (o.isSource(o.out) || o.isSource(o.ours)) return o.merge();
197
+ if (o.fallback === void 0) {
198
+ o.error(`${o.tool} mergetool: ${o.out} is not ${o.tool} source and no --fallback is configured`);
199
+ return 2;
200
+ }
201
+ const [cmd, ...pre] = o.fallback.split(/\s+/).filter(Boolean);
202
+ if (cmd === void 0) {
203
+ o.error(`${o.tool} mergetool: --fallback is empty`);
204
+ return 2;
205
+ }
206
+ const r = child_process.spawnSync(cmd, [...pre, o.base, o.theirs, o.ours, o.out], { stdio: "inherit" });
207
+ if (r.error) {
208
+ o.error(`${o.tool} mergetool: could not run the fallback '${cmd}': ${r.error.message}`);
209
+ return 1;
210
+ }
211
+ return r.status ?? 1;
212
+ }
213
+
214
+ exports.VCS_KINDS = VCS_KINDS;
215
+ exports.VCS_LABELS = VCS_LABELS;
216
+ exports.VCS_SETUP_FILE = VCS_SETUP_FILE;
217
+ exports.bundlePostureOf = bundlePostureOf;
218
+ exports.ignoreFileFor = ignoreFileFor;
219
+ exports.isVcsKind = isVcsKind;
220
+ exports.runMergetool = runMergetool;
221
+ exports.vcsConfigFiles = vcsConfigFiles;
222
+ //# sourceMappingURL=vcs.cjs.map
223
+ //# sourceMappingURL=vcs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vcs.ts"],"names":["spawnSync"],"mappings":"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","file":"vcs.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Version-control setup for a project: the files `init` writes for the team's\n// version control system, the bundle posture, and the `mergetool` wrapper a\n// system with one global merge tool calls.\n//\n// Lifted from Patter (patter/packages/ops/src/init.ts and the CLI's\n// `mergetool`), where git, Perforce, Plastic SCM and SVN were all covered, so\n// the Storylet format takes the same support rather than a second copy of it\n// (patterkit design/family-parity-2026-10.md item 13). Each format says what\n// it is called and what its files are; the rules and the words are shared.\n//\n// A subpath of its own because `runMergetool` starts the fallback tool, which\n// needs `node:child_process`; the rest is plain text.\n// ---------------------------------------------------------------------------\n\nimport { spawnSync } from \"node:child_process\";\n\n/** The version control systems a project can be set up for. */\nexport const VCS_KINDS = [\"git\", \"perforce\", \"plastic\", \"svn\"] as const;\nexport type VcsKind = typeof VCS_KINDS[number];\n\nexport const isVcsKind = (value: unknown): value is VcsKind =>\n typeof value === \"string\" && (VCS_KINDS as readonly string[]).includes(value);\n\n/** What each system is called to a person, for pickers and messages. */\nexport const VCS_LABELS: Readonly<Record<VcsKind, string>> = {\n git: \"Git\", perforce: \"Perforce\", plastic: \"Plastic SCM\", svn: \"Subversion (SVN)\",\n};\n\n/** Whether the compiled bundle is committed, or built in CI and ignored. */\nexport type BundlePosture = \"commit\" | \"ignore\";\n\n/** What a format is called and what its files are. Extensions are without the dot. */\nexport interface VcsFormat {\n /** What the guide calls the source: \"Patter\", \"Storylet Studio\". */\n readonly product: string;\n /** The command-line tool: \"patter\", \"storyletengine\". */\n readonly tool: string;\n /** The git merge driver's name, as `.gitattributes` names it: \"patter\", \"storylets\". */\n readonly driver: string;\n /** The source shards' extensions: UTF-8 + LF text, merged by the structured merge. */\n readonly source: readonly string[];\n /** An extension manifest's suffix (`.extension.json`), and the folder that holds them: text, merged as text. */\n readonly manifest?: { readonly dir: string; readonly suffix: string };\n /** The compiled bundle's extension: regenerated, never hand-merged. */\n readonly bundle: string;\n /** The packed document's extension: a binary zip, kept out of version control. */\n readonly pack: string;\n /** The unresolved-merge sidecar's extension: never committed. */\n readonly conflict: string;\n /** Further binary paths for git, each with the line saying why (`assets/**`). */\n readonly binary?: readonly { readonly pattern: string; readonly note: string }[];\n}\n\n/** One file to write, relative to the project folder. */\nexport interface VcsFile {\n readonly path: string;\n readonly content: string;\n}\n\n/** The setup guide every project gets, whatever its system. */\nexport const VCS_SETUP_FILE = \"vcs-setup.md\";\n\n/** The ignore file a system reads, or undefined for one that keeps its ignores elsewhere (SVN: a property). */\nexport function ignoreFileFor(vcs: VcsKind | undefined): string | undefined {\n switch (vcs) {\n case \"git\": return \".gitignore\";\n case \"perforce\": return \".p4ignore\";\n case \"plastic\": return \"ignore.conf\";\n default: return undefined;\n }\n}\n\n/**\n * The files a project is set up with for `vcs`: the setup guide always, git's\n * `.gitattributes`, and the system's ignore file. With no system, the guide\n * carries every system's section and nothing else is written.\n *\n * Shared by `init` and an editor's switch of system, which writes these for the\n * new one and removes what the old one's list holds that this one's does not.\n */\nexport function vcsConfigFiles(format: VcsFormat, vcs: VcsKind | undefined, bundle: BundlePosture = \"commit\"): VcsFile[] {\n const files: VcsFile[] = [{ path: VCS_SETUP_FILE, content: vcsSetup(format, vcs, bundle) }];\n if (vcs === \"git\") files.push({ path: \".gitattributes\", content: gitAttributes(format) });\n const ignore = ignoreFileFor(vcs);\n if (ignore !== undefined) files.push({ path: ignore, content: ignoreContent(format, bundle) });\n return files;\n}\n\n/**\n * The bundle posture a project was set up with, read back from the files\n * `vcsConfigFiles` wrote: the ignore file naming the bundle, else the guide\n * saying the project ignores it (SVN keeps its ignores in a property). \"commit\"\n * when neither says so. `read` returns a project-relative file's text, or\n * undefined when it is not there.\n *\n * A switch of system re-writes the files, and always writing \"commit\" lost the\n * ignore rule of a project set up to ignore its bundle (Patter, before this).\n */\nexport function bundlePostureOf(format: VcsFormat, vcs: VcsKind | undefined, read: (path: string) => string | undefined): BundlePosture {\n const ignore = ignoreFileFor(vcs);\n if (ignore !== undefined && (read(ignore) ?? \"\").split(/\\r?\\n/).some((l) => l.trim() === `*.${format.bundle}`)) return \"ignore\";\n return (read(VCS_SETUP_FILE) ?? \"\").includes(IGNORES_MARK) ? \"ignore\" : \"commit\";\n}\n\n// --- the files -----------------------------------------------------------------\n\nconst IGNORES_MARK = \"This project IGNORES the compiled\";\n\nconst column = (patterns: readonly string[]): number => Math.max(...patterns.map((p) => p.length)) + 1;\n\nfunction gitAttributes(f: VcsFormat): string {\n const source = f.source.map((ext) => `*.${ext}`);\n const manifest = f.manifest !== undefined ? `${f.manifest.dir}/*${f.manifest.suffix}` : undefined;\n // Aligned on the extension patterns; a longer path pattern takes one space.\n const w = column([...source, `*.${f.bundle}`, `*.${f.pack}`]);\n const row = (pattern: string, attrs: string): string => `${pattern.padEnd(w)}${pattern.length >= w ? \" \" : \"\"}${attrs}`;\n const blocks = [\n `# ${f.product} source is UTF-8 + LF text (pinned; never let autocrlf touch it).\\n`\n + source.map((p) => row(p, \"text eol=lf\")).join(\"\\n\"),\n `# Id-keyed structured merge for ${f.product} source (the '${f.tool} merge' driver).\\n`\n + `# Register it once per clone (see ${VCS_SETUP_FILE}); until then git falls back to a\\n`\n + `# normal text merge for these: no harm, just not structure-aware.\\n`\n + source.map((p) => row(p, `merge=${f.driver}`)).join(\"\\n\"),\n ];\n if (manifest !== undefined) {\n blocks.push(`# An extension's manifest is the extension's own JSON, kept in the project so\\n`\n + `# every machine reads the same one, and never edited here: text, merged as text.\\n`\n + row(manifest, \"text eol=lf\"));\n }\n blocks.push(`# Generated artifacts. The compiled bundle is committed but REGENERATED, never\\n`\n + `# hand-merged: keep ours on conflict and rebuild ('${f.tool} validate' catches a\\n`\n + `# stale one). Needs a one-time 'git config merge.ours.driver true' (see ${VCS_SETUP_FILE}).\\n`\n + row(`*.${f.bundle}`, \"text eol=lf merge=ours\") + \"\\n\"\n + `# The packed document is a binary zip envelope, kept out of version control (.gitignore).\\n`\n + row(`*.${f.pack}`, \"binary\"));\n for (const b of f.binary ?? []) blocks.push(`${b.note.split(\"\\n\").map((l) => `# ${l}`).join(\"\\n\")}\\n${row(b.pattern, \"binary\")}`);\n return blocks.join(\"\\n\\n\") + \"\\n\";\n}\n\nfunction ignoreContent(f: VcsFormat, bundle: BundlePosture): string {\n const lines = [\n `# ${f.product} generated artifacts: not source (see ${VCS_SETUP_FILE}).`,\n \"# Packed document (the send-and-return envelope):\",\n `*.${f.pack}`,\n \"# Unresolved merge sidecar (a lingering one is a validate error). The merge also names its clashes\",\n \"# in the shard itself, under \\\"unresolved\\\", which IS committed with the file, so CI refuses an\",\n \"# unresolved merge wherever it goes:\",\n `*.${f.conflict}`,\n ];\n if (bundle === \"ignore\") lines.push(\"# Compiled bundle (built in CI, not committed under this posture):\", `*.${f.bundle}`);\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction vcsSetup(f: VcsFormat, vcs: VcsKind | undefined, bundle: BundlePosture): string {\n const text = [...f.source, ...(f.manifest !== undefined ? [f.manifest.suffix.replace(/^\\./, \"\")] : []), f.bundle];\n const sections: Record<VcsKind, string> = {\n git: `## git\n\n\\`.gitattributes\\` (already emitted) pins ${f.product} source shards to UTF-8 + LF\ntext and marks the generated artifacts (the \\`.${f.bundle}\\` bundle \\`merge=ours\\`, the\n\\`.${f.pack}\\` document \\`binary\\`). \\`.gitignore\\` (already emitted) keeps the document\n(and the bundle, under the \"ignore\" posture) out of source control.\n\nRecommended pre-commit hook (\\`.git/hooks/pre-commit\\`, executable):\n\n #!/bin/sh\n ${f.tool} validate || exit 1\n\nThe \\`merge=${f.driver}\\` rules in \\`.gitattributes\\` are already active; register the\ndrivers once per clone (git config is not repo-tracked). Until then git falls\nback to a normal text merge for those files:\n\n git config merge.${f.driver}.name \"${f.product} structured merge\"\n git config merge.${f.driver}.driver \"${f.tool} merge %O %A %B -o %A --path %P\"\n git config merge.ours.driver true\n\ngit calls the per-path driver directly (no \\`mergetool\\` wrapper needed). \\`%O %A %B\\`\nare base, ours and theirs; the merged result is written back to \\`%A\\`, and \\`%P\\` is\nthe file's real path. On a conflict \\`${f.tool} merge\\` exits non-zero and writes a\n\\`.${f.conflict}\\` sidecar beside that file, so the merge stays unresolved.\n`,\n perforce: `## Perforce\n\nAdd ${f.product}'s extensions to the typemap (\\`p4 typemap\\`): source shards and\nthe compiled bundle are TEXT with LF; the packed document is BINARY:\n\n${text.map((ext) => ` text //....${ext}`).join(\"\\n\")}\n binary //....${f.pack}\n\nOn a unicode-mode server, set \\`P4CHARSET=utf8\\`. For a lock-based workflow (one\nlock per file), add \\`+l\\` to make checkouts exclusive. A \\`.p4ignore\\` (already\nemitted) keeps the packed document out of the depot.\n\nPerforce allows ONE global merge tool, so set \\`${f.tool} mergetool\\` as it: it\nruns the structured merge for ${f.product} source and hands everything else to\nyour normal tool. Map Perforce's variables into the order BASE THEIRS OURS OUT:\n\n ${f.tool} mergetool --fallback \"<your merge tool>\" %b %t %y %r\n\n(\\`%b\\` base, \\`%t\\` theirs, \\`%y\\` yours, \\`%r\\` result: adjust to your P4 client's\nvariable names; the argument ORDER is what matters.)\n`,\n plastic: `## Plastic SCM (Unity Version Control)\n\n${f.product} source shards are plain UTF-8 text; Plastic handles them as they\nare. The \\`ignore.conf\\` (already emitted) keeps the packed document out of the\nrepository. For a lock-based workflow, configure exclusive checkout (lock.conf)\nfor the shard extensions.\n\nIn Preferences > Merge tools, add an external tool (a global entry is fine: the\nwrapper looks at each file's name) with the arguments in BASE THEIRS OURS OUT\norder:\n\n ${f.tool} mergetool --fallback \"<your merge tool>\" @basefile @sourcefile @destinationfile @output\n`,\n svn: `## SVN\n\nSet auto-props so ${f.product} source keeps LF (\\`~/.subversion/config\\` or the\nrepository's config):\n\n [auto-props]\n${text.map((ext) => ` *.${ext} = svn:eol-style=LF`).join(\"\\n\")}\n *.${f.pack} = svn:mime-type=application/octet-stream\n\nSVN's ignore is a folder PROPERTY, not a file; set it from the project folder:\n\n svn propset svn:ignore \"*.${f.pack}${bundle === \"ignore\" ? `\\\\n*.${f.bundle}` : \"\"}\\\\n*.${f.conflict}\" .\n\nFor a lock-based workflow, add \\`svn:needs-lock\\` to the shard patterns.\n\nSVN allows one global merge tool. Point \\`[helpers] merge-tool-cmd\\` at a small\nwrapper script that passes SVN's four arguments (base, theirs, mine, merged: BASE\nTHEIRS OURS OUT) to \\`${f.tool} mergetool\\`:\n\n #!/bin/sh\n exec ${f.tool} mergetool --fallback \"<your merge tool>\" \"$1\" \"$2\" \"$3\" \"$4\"\n`,\n };\n const head = vcs !== undefined\n ? sections[vcs]\n : `No version control system chosen (\\`${f.tool} init --vcs ${VCS_KINDS.join(\"|\")}\\` writes\nthe setup for one). Whichever you adopt, the rules are the same: ${f.product} source\nis UTF-8 + LF text, and \\`${f.tool} validate\\` belongs in your pre-commit hook and\nCI. Sections for every supported system:\n\n${VCS_KINDS.map((k) => sections[k]).join(\"\\n\")}`;\n return `# Version-control setup for this ${f.product} project\\n\\n${head}\\n${bundleSection(f, bundle)}`;\n}\n\nfunction bundleSection(f: VcsFormat, bundle: BundlePosture): string {\n const posture = bundle === \"commit\"\n ? `This project COMMITS the compiled \\`.${f.bundle}\\` bundle (\\`${f.tool} init --bundle ignore\\`\nbuilds it in CI instead). The bundle is regenerated, never hand-merged: on a\nconflict, keep ours and run \\`${f.tool} export\\` again. \\`${f.tool} validate\\` works out\nthe bundle's hash from source and FAILS if it is stale, so a forgotten rebuild\ncannot ship unnoticed.`\n : `${IGNORES_MARK} \\`.${f.bundle}\\` bundle (built in CI; \\`${f.tool} init\n--bundle commit\\` commits it instead). Run \\`${f.tool} export\\` as a build step.`;\n return `## Compiled bundle and packed document\n\n${posture}\n\nThe packed \\`.${f.pack}\\` document (\\`${f.tool} pack\\`) is the send-and-return envelope\nfor collaborators without version control. It is a binary zip and a projection of\nthe shards, NOT source, so it stays out of version control (ignored above); edits\ncome back with \\`${f.tool} unpack\\`.\n`;\n}\n\n// --- mergetool -------------------------------------------------------------------\n\nexport interface MergetoolOptions {\n /** The four files, in the order every system with one global tool passes them. */\n readonly base: string;\n readonly theirs: string;\n readonly ours: string;\n readonly out: string;\n /** The team's ordinary merge tool, with any flags of its own (\"code --wait --merge\"). */\n readonly fallback?: string;\n /** Is this path the format's source? Asked of `out` and of `ours`. */\n readonly isSource: (path: string) => boolean;\n /** Run the structured merge of base, ours and theirs into `out`; its exit code. */\n readonly merge: () => number;\n /** Where to say what went wrong. */\n readonly error: (message: string) => void;\n /** The tool's name, for messages: \"patter\", \"storyletengine\". */\n readonly tool: string;\n}\n\n/**\n * The sniff-and-dispatch wrapper: one global external merge tool serves the\n * whole depot (Perforce, Plastic SCM and SVN allow only one). The format's\n * source goes to the structured merge; anything else goes to `fallback`, with\n * the four paths as separate arguments and no shell, so spaces and\n * metacharacters in them are safe. git does not need this: it calls the\n * per-path driver directly.\n *\n * Exit codes: the merge's, the fallback's, 1 when the fallback could not\n * start, 2 when a file is not the format's and no fallback is configured.\n */\nexport function runMergetool(o: MergetoolOptions): number {\n if (o.isSource(o.out) || o.isSource(o.ours)) return o.merge();\n if (o.fallback === undefined) {\n o.error(`${o.tool} mergetool: ${o.out} is not ${o.tool} source and no --fallback is configured`);\n return 2;\n }\n const [cmd, ...pre] = o.fallback.split(/\\s+/).filter(Boolean);\n if (cmd === undefined) {\n o.error(`${o.tool} mergetool: --fallback is empty`);\n return 2;\n }\n const r = spawnSync(cmd, [...pre, o.base, o.theirs, o.ours, o.out], { stdio: \"inherit\" });\n // A tool that could not start (not installed, not on PATH) has no status: say so, or the merge just fails.\n if (r.error) {\n o.error(`${o.tool} mergetool: could not run the fallback '${cmd}': ${r.error.message}`);\n return 1;\n }\n return r.status ?? 1;\n}\n"]}
package/dist/vcs.d.cts ADDED
@@ -0,0 +1,95 @@
1
+ /** The version control systems a project can be set up for. */
2
+ declare const VCS_KINDS: readonly ["git", "perforce", "plastic", "svn"];
3
+ type VcsKind = typeof VCS_KINDS[number];
4
+ declare const isVcsKind: (value: unknown) => value is VcsKind;
5
+ /** What each system is called to a person, for pickers and messages. */
6
+ declare const VCS_LABELS: Readonly<Record<VcsKind, string>>;
7
+ /** Whether the compiled bundle is committed, or built in CI and ignored. */
8
+ type BundlePosture = "commit" | "ignore";
9
+ /** What a format is called and what its files are. Extensions are without the dot. */
10
+ interface VcsFormat {
11
+ /** What the guide calls the source: "Patter", "Storylet Studio". */
12
+ readonly product: string;
13
+ /** The command-line tool: "patter", "storyletengine". */
14
+ readonly tool: string;
15
+ /** The git merge driver's name, as `.gitattributes` names it: "patter", "storylets". */
16
+ readonly driver: string;
17
+ /** The source shards' extensions: UTF-8 + LF text, merged by the structured merge. */
18
+ readonly source: readonly string[];
19
+ /** An extension manifest's suffix (`.extension.json`), and the folder that holds them: text, merged as text. */
20
+ readonly manifest?: {
21
+ readonly dir: string;
22
+ readonly suffix: string;
23
+ };
24
+ /** The compiled bundle's extension: regenerated, never hand-merged. */
25
+ readonly bundle: string;
26
+ /** The packed document's extension: a binary zip, kept out of version control. */
27
+ readonly pack: string;
28
+ /** The unresolved-merge sidecar's extension: never committed. */
29
+ readonly conflict: string;
30
+ /** Further binary paths for git, each with the line saying why (`assets/**`). */
31
+ readonly binary?: readonly {
32
+ readonly pattern: string;
33
+ readonly note: string;
34
+ }[];
35
+ }
36
+ /** One file to write, relative to the project folder. */
37
+ interface VcsFile {
38
+ readonly path: string;
39
+ readonly content: string;
40
+ }
41
+ /** The setup guide every project gets, whatever its system. */
42
+ declare const VCS_SETUP_FILE = "vcs-setup.md";
43
+ /** The ignore file a system reads, or undefined for one that keeps its ignores elsewhere (SVN: a property). */
44
+ declare function ignoreFileFor(vcs: VcsKind | undefined): string | undefined;
45
+ /**
46
+ * The files a project is set up with for `vcs`: the setup guide always, git's
47
+ * `.gitattributes`, and the system's ignore file. With no system, the guide
48
+ * carries every system's section and nothing else is written.
49
+ *
50
+ * Shared by `init` and an editor's switch of system, which writes these for the
51
+ * new one and removes what the old one's list holds that this one's does not.
52
+ */
53
+ declare function vcsConfigFiles(format: VcsFormat, vcs: VcsKind | undefined, bundle?: BundlePosture): VcsFile[];
54
+ /**
55
+ * The bundle posture a project was set up with, read back from the files
56
+ * `vcsConfigFiles` wrote: the ignore file naming the bundle, else the guide
57
+ * saying the project ignores it (SVN keeps its ignores in a property). "commit"
58
+ * when neither says so. `read` returns a project-relative file's text, or
59
+ * undefined when it is not there.
60
+ *
61
+ * A switch of system re-writes the files, and always writing "commit" lost the
62
+ * ignore rule of a project set up to ignore its bundle (Patter, before this).
63
+ */
64
+ declare function bundlePostureOf(format: VcsFormat, vcs: VcsKind | undefined, read: (path: string) => string | undefined): BundlePosture;
65
+ interface MergetoolOptions {
66
+ /** The four files, in the order every system with one global tool passes them. */
67
+ readonly base: string;
68
+ readonly theirs: string;
69
+ readonly ours: string;
70
+ readonly out: string;
71
+ /** The team's ordinary merge tool, with any flags of its own ("code --wait --merge"). */
72
+ readonly fallback?: string;
73
+ /** Is this path the format's source? Asked of `out` and of `ours`. */
74
+ readonly isSource: (path: string) => boolean;
75
+ /** Run the structured merge of base, ours and theirs into `out`; its exit code. */
76
+ readonly merge: () => number;
77
+ /** Where to say what went wrong. */
78
+ readonly error: (message: string) => void;
79
+ /** The tool's name, for messages: "patter", "storyletengine". */
80
+ readonly tool: string;
81
+ }
82
+ /**
83
+ * The sniff-and-dispatch wrapper: one global external merge tool serves the
84
+ * whole depot (Perforce, Plastic SCM and SVN allow only one). The format's
85
+ * source goes to the structured merge; anything else goes to `fallback`, with
86
+ * the four paths as separate arguments and no shell, so spaces and
87
+ * metacharacters in them are safe. git does not need this: it calls the
88
+ * per-path driver directly.
89
+ *
90
+ * Exit codes: the merge's, the fallback's, 1 when the fallback could not
91
+ * start, 2 when a file is not the format's and no fallback is configured.
92
+ */
93
+ declare function runMergetool(o: MergetoolOptions): number;
94
+
95
+ export { type BundlePosture, type MergetoolOptions, VCS_KINDS, VCS_LABELS, VCS_SETUP_FILE, type VcsFile, type VcsFormat, type VcsKind, bundlePostureOf, ignoreFileFor, isVcsKind, runMergetool, vcsConfigFiles };
package/dist/vcs.d.ts ADDED
@@ -0,0 +1,95 @@
1
+ /** The version control systems a project can be set up for. */
2
+ declare const VCS_KINDS: readonly ["git", "perforce", "plastic", "svn"];
3
+ type VcsKind = typeof VCS_KINDS[number];
4
+ declare const isVcsKind: (value: unknown) => value is VcsKind;
5
+ /** What each system is called to a person, for pickers and messages. */
6
+ declare const VCS_LABELS: Readonly<Record<VcsKind, string>>;
7
+ /** Whether the compiled bundle is committed, or built in CI and ignored. */
8
+ type BundlePosture = "commit" | "ignore";
9
+ /** What a format is called and what its files are. Extensions are without the dot. */
10
+ interface VcsFormat {
11
+ /** What the guide calls the source: "Patter", "Storylet Studio". */
12
+ readonly product: string;
13
+ /** The command-line tool: "patter", "storyletengine". */
14
+ readonly tool: string;
15
+ /** The git merge driver's name, as `.gitattributes` names it: "patter", "storylets". */
16
+ readonly driver: string;
17
+ /** The source shards' extensions: UTF-8 + LF text, merged by the structured merge. */
18
+ readonly source: readonly string[];
19
+ /** An extension manifest's suffix (`.extension.json`), and the folder that holds them: text, merged as text. */
20
+ readonly manifest?: {
21
+ readonly dir: string;
22
+ readonly suffix: string;
23
+ };
24
+ /** The compiled bundle's extension: regenerated, never hand-merged. */
25
+ readonly bundle: string;
26
+ /** The packed document's extension: a binary zip, kept out of version control. */
27
+ readonly pack: string;
28
+ /** The unresolved-merge sidecar's extension: never committed. */
29
+ readonly conflict: string;
30
+ /** Further binary paths for git, each with the line saying why (`assets/**`). */
31
+ readonly binary?: readonly {
32
+ readonly pattern: string;
33
+ readonly note: string;
34
+ }[];
35
+ }
36
+ /** One file to write, relative to the project folder. */
37
+ interface VcsFile {
38
+ readonly path: string;
39
+ readonly content: string;
40
+ }
41
+ /** The setup guide every project gets, whatever its system. */
42
+ declare const VCS_SETUP_FILE = "vcs-setup.md";
43
+ /** The ignore file a system reads, or undefined for one that keeps its ignores elsewhere (SVN: a property). */
44
+ declare function ignoreFileFor(vcs: VcsKind | undefined): string | undefined;
45
+ /**
46
+ * The files a project is set up with for `vcs`: the setup guide always, git's
47
+ * `.gitattributes`, and the system's ignore file. With no system, the guide
48
+ * carries every system's section and nothing else is written.
49
+ *
50
+ * Shared by `init` and an editor's switch of system, which writes these for the
51
+ * new one and removes what the old one's list holds that this one's does not.
52
+ */
53
+ declare function vcsConfigFiles(format: VcsFormat, vcs: VcsKind | undefined, bundle?: BundlePosture): VcsFile[];
54
+ /**
55
+ * The bundle posture a project was set up with, read back from the files
56
+ * `vcsConfigFiles` wrote: the ignore file naming the bundle, else the guide
57
+ * saying the project ignores it (SVN keeps its ignores in a property). "commit"
58
+ * when neither says so. `read` returns a project-relative file's text, or
59
+ * undefined when it is not there.
60
+ *
61
+ * A switch of system re-writes the files, and always writing "commit" lost the
62
+ * ignore rule of a project set up to ignore its bundle (Patter, before this).
63
+ */
64
+ declare function bundlePostureOf(format: VcsFormat, vcs: VcsKind | undefined, read: (path: string) => string | undefined): BundlePosture;
65
+ interface MergetoolOptions {
66
+ /** The four files, in the order every system with one global tool passes them. */
67
+ readonly base: string;
68
+ readonly theirs: string;
69
+ readonly ours: string;
70
+ readonly out: string;
71
+ /** The team's ordinary merge tool, with any flags of its own ("code --wait --merge"). */
72
+ readonly fallback?: string;
73
+ /** Is this path the format's source? Asked of `out` and of `ours`. */
74
+ readonly isSource: (path: string) => boolean;
75
+ /** Run the structured merge of base, ours and theirs into `out`; its exit code. */
76
+ readonly merge: () => number;
77
+ /** Where to say what went wrong. */
78
+ readonly error: (message: string) => void;
79
+ /** The tool's name, for messages: "patter", "storyletengine". */
80
+ readonly tool: string;
81
+ }
82
+ /**
83
+ * The sniff-and-dispatch wrapper: one global external merge tool serves the
84
+ * whole depot (Perforce, Plastic SCM and SVN allow only one). The format's
85
+ * source goes to the structured merge; anything else goes to `fallback`, with
86
+ * the four paths as separate arguments and no shell, so spaces and
87
+ * metacharacters in them are safe. git does not need this: it calls the
88
+ * per-path driver directly.
89
+ *
90
+ * Exit codes: the merge's, the fallback's, 1 when the fallback could not
91
+ * start, 2 when a file is not the format's and no fallback is configured.
92
+ */
93
+ declare function runMergetool(o: MergetoolOptions): number;
94
+
95
+ export { type BundlePosture, type MergetoolOptions, VCS_KINDS, VCS_LABELS, VCS_SETUP_FILE, type VcsFile, type VcsFormat, type VcsKind, bundlePostureOf, ignoreFileFor, isVcsKind, runMergetool, vcsConfigFiles };