first-tree 0.0.2 → 0.0.4
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/README.md +116 -40
- package/dist/cli.js +46 -17
- package/dist/help-Dtdj91HJ.js +25 -0
- package/dist/init--VepFe6N.js +403 -0
- package/dist/installer-cH7N4RNj.js +47 -0
- package/dist/onboarding-C9cYSE6F.js +2 -0
- package/dist/onboarding-CPP8fF4D.js +10 -0
- package/dist/repo-DY57bMqr.js +318 -0
- package/dist/upgrade-Cgx_K2HM.js +135 -0
- package/dist/{verify-CSRIkuoM.js → verify-mC9ZTd1f.js} +118 -29
- package/package.json +33 -10
- package/skills/first-tree/SKILL.md +113 -0
- package/skills/first-tree/agents/openai.yaml +4 -0
- package/skills/first-tree/assets/framework/VERSION +1 -0
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +14 -0
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +14 -0
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +224 -0
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +15 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +193 -0
- package/skills/first-tree/assets/framework/manifest.json +11 -0
- package/skills/first-tree/assets/framework/prompts/pr-review.md +38 -0
- package/skills/first-tree/assets/framework/templates/agents.md.template +49 -0
- package/skills/first-tree/assets/framework/templates/member-node.md.template +18 -0
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +45 -0
- package/skills/first-tree/assets/framework/templates/root-node.md.template +41 -0
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +31 -0
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +146 -0
- package/skills/first-tree/assets/framework/workflows/validate.yml +19 -0
- package/skills/first-tree/engine/commands/help.ts +32 -0
- package/skills/first-tree/engine/commands/init.ts +1 -0
- package/skills/first-tree/engine/commands/upgrade.ts +1 -0
- package/skills/first-tree/engine/commands/verify.ts +1 -0
- package/skills/first-tree/engine/init.ts +414 -0
- package/skills/first-tree/engine/onboarding.ts +10 -0
- package/skills/first-tree/engine/repo.ts +360 -0
- package/skills/first-tree/engine/rules/agent-instructions.ts +59 -0
- package/skills/first-tree/engine/rules/agent-integration.ts +19 -0
- package/skills/first-tree/engine/rules/ci-validation.ts +72 -0
- package/skills/first-tree/engine/rules/framework.ts +13 -0
- package/skills/first-tree/engine/rules/index.ts +41 -0
- package/skills/first-tree/engine/rules/members.ts +21 -0
- package/skills/first-tree/engine/rules/populate-tree.ts +36 -0
- package/skills/first-tree/engine/rules/root-node.ts +41 -0
- package/skills/first-tree/engine/runtime/adapters.ts +22 -0
- package/skills/first-tree/engine/runtime/asset-loader.ts +141 -0
- package/skills/first-tree/engine/runtime/installer.ts +82 -0
- package/skills/first-tree/engine/runtime/upgrader.ts +23 -0
- package/skills/first-tree/engine/upgrade.ts +233 -0
- package/skills/first-tree/engine/validators/members.ts +215 -0
- package/skills/first-tree/engine/validators/nodes.ts +559 -0
- package/skills/first-tree/engine/verify.ts +155 -0
- package/skills/first-tree/references/about.md +36 -0
- package/skills/first-tree/references/maintainer-architecture.md +59 -0
- package/skills/first-tree/references/maintainer-build-and-distribution.md +59 -0
- package/skills/first-tree/references/maintainer-testing.md +58 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +38 -0
- package/skills/first-tree/references/onboarding.md +185 -0
- package/skills/first-tree/references/ownership-and-naming.md +94 -0
- package/skills/first-tree/references/principles.md +113 -0
- package/skills/first-tree/references/source-map.md +94 -0
- package/skills/first-tree/references/upgrade-contract.md +94 -0
- package/skills/first-tree/scripts/check-skill-sync.sh +133 -0
- package/skills/first-tree/scripts/quick_validate.py +95 -0
- package/skills/first-tree/scripts/run-local-cli.sh +35 -0
- package/skills/first-tree/tests/asset-loader.test.ts +75 -0
- package/skills/first-tree/tests/generate-codeowners.test.ts +94 -0
- package/skills/first-tree/tests/helpers.ts +169 -0
- package/skills/first-tree/tests/init.test.ts +250 -0
- package/skills/first-tree/tests/repo.test.ts +440 -0
- package/skills/first-tree/tests/rules.test.ts +413 -0
- package/skills/first-tree/tests/run-review.test.ts +155 -0
- package/skills/first-tree/tests/skill-artifacts.test.ts +311 -0
- package/skills/first-tree/tests/thin-cli.test.ts +104 -0
- package/skills/first-tree/tests/upgrade.test.ts +103 -0
- package/skills/first-tree/tests/validate-members.test.ts +224 -0
- package/skills/first-tree/tests/validate-nodes.test.ts +198 -0
- package/skills/first-tree/tests/verify.test.ts +241 -0
- package/dist/init-CE_944sb.js +0 -283
- package/dist/repo-BByc3VvM.js +0 -111
- package/dist/upgrade-Chr7z0CY.js +0 -82
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
10
|
+
import { Repo } from "#skill/engine/repo.js";
|
|
11
|
+
import { ONBOARDING_TEXT } from "#skill/engine/onboarding.js";
|
|
12
|
+
import { evaluateAll } from "#skill/engine/rules/index.js";
|
|
13
|
+
import type { RuleResult } from "#skill/engine/rules/index.js";
|
|
14
|
+
import {
|
|
15
|
+
copyCanonicalSkill,
|
|
16
|
+
renderTemplateFile,
|
|
17
|
+
resolveBundledPackageRoot,
|
|
18
|
+
} from "#skill/engine/runtime/installer.js";
|
|
19
|
+
import {
|
|
20
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
21
|
+
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
22
|
+
FRAMEWORK_ASSET_ROOT,
|
|
23
|
+
FRAMEWORK_VERSION,
|
|
24
|
+
INSTALLED_PROGRESS,
|
|
25
|
+
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
26
|
+
} from "#skill/engine/runtime/asset-loader.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The interactive prompt tool the agent should use to present choices.
|
|
30
|
+
* Different agents may name this differently — change it here to update
|
|
31
|
+
* all generated task text at once.
|
|
32
|
+
*/
|
|
33
|
+
export const INTERACTIVE_TOOL = "AskUserQuestion";
|
|
34
|
+
export const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
|
|
35
|
+
|
|
36
|
+
By default, running \`context-tree init\` inside a source or workspace repo creates
|
|
37
|
+
a sibling dedicated tree repo named \`<repo>-context\`.
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--here Initialize the current repo in place
|
|
41
|
+
--tree-name NAME Name the dedicated sibling tree repo to create
|
|
42
|
+
--tree-path PATH Use an explicit tree repo path
|
|
43
|
+
--help Show this help message
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
interface TemplateTarget {
|
|
47
|
+
templateName: string;
|
|
48
|
+
targetPath: string;
|
|
49
|
+
skipIfExists?: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const TEMPLATE_MAP: TemplateTarget[] = [
|
|
53
|
+
{ templateName: "root-node.md.template", targetPath: "NODE.md" },
|
|
54
|
+
{
|
|
55
|
+
templateName: AGENT_INSTRUCTIONS_TEMPLATE,
|
|
56
|
+
targetPath: AGENT_INSTRUCTIONS_FILE,
|
|
57
|
+
skipIfExists: [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE],
|
|
58
|
+
},
|
|
59
|
+
{ templateName: "members-domain.md.template", targetPath: "members/NODE.md" },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
interface TaskListContext {
|
|
63
|
+
sourceRepoPath?: string;
|
|
64
|
+
dedicatedTreeRepo?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function installSkill(source: string, target: string): void {
|
|
68
|
+
copyCanonicalSkill(source, target);
|
|
69
|
+
console.log(
|
|
70
|
+
" Installed skills/first-tree/ from the bundled first-tree package",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderTemplates(target: string): void {
|
|
75
|
+
const frameworkDir = join(target, FRAMEWORK_ASSET_ROOT);
|
|
76
|
+
for (const { templateName, targetPath, skipIfExists } of TEMPLATE_MAP) {
|
|
77
|
+
const existingPaths = skipIfExists ?? [targetPath];
|
|
78
|
+
const existingPath = existingPaths.find((candidate) =>
|
|
79
|
+
existsSync(join(target, candidate)),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (existingPath !== undefined) {
|
|
83
|
+
console.log(` Skipped ${targetPath} (found existing ${existingPath})`);
|
|
84
|
+
} else if (renderTemplateFile(frameworkDir, templateName, target, targetPath)) {
|
|
85
|
+
console.log(` Created ${targetPath}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatTaskList(
|
|
91
|
+
groups: RuleResult[],
|
|
92
|
+
context?: TaskListContext,
|
|
93
|
+
): string {
|
|
94
|
+
const lines: string[] = [
|
|
95
|
+
"# Context Tree Init\n",
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (context?.dedicatedTreeRepo) {
|
|
99
|
+
lines.push(
|
|
100
|
+
"This repository is the dedicated Context Tree. Keep decisions, rationale," +
|
|
101
|
+
" cross-domain relationships, and ownership here; keep execution detail" +
|
|
102
|
+
" in your source repositories.",
|
|
103
|
+
"",
|
|
104
|
+
);
|
|
105
|
+
if (context.sourceRepoPath) {
|
|
106
|
+
lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
|
|
107
|
+
}
|
|
108
|
+
lines.push(
|
|
109
|
+
"When you publish this tree repo, keep it in the same GitHub organization" +
|
|
110
|
+
" as the source repo unless you have a reason not to.",
|
|
111
|
+
"",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lines.push(
|
|
116
|
+
"**Agent instructions:** Before starting work, analyze the full task list below and" +
|
|
117
|
+
" identify all information you need from the user. Ask the user for their code" +
|
|
118
|
+
" repositories or project directories so you can analyze the source yourself —" +
|
|
119
|
+
" derive project descriptions, domains, and members from the code instead of" +
|
|
120
|
+
" asking the user to describe them. Collect everything upfront using the" +
|
|
121
|
+
` **${INTERACTIVE_TOOL}** tool with structured options — present selectable choices` +
|
|
122
|
+
" (with label and description) so the user can pick instead of typing free-form" +
|
|
123
|
+
` answers. You may batch up to 4 questions per ${INTERACTIVE_TOOL} call.\n`,
|
|
124
|
+
);
|
|
125
|
+
for (const group of groups) {
|
|
126
|
+
lines.push(`## ${group.group}`);
|
|
127
|
+
for (const task of group.tasks) {
|
|
128
|
+
lines.push(`- [ ] ${task}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push("");
|
|
131
|
+
}
|
|
132
|
+
lines.push("## Verification");
|
|
133
|
+
lines.push(
|
|
134
|
+
"After completing the tasks above, run `context-tree verify` to confirm:",
|
|
135
|
+
);
|
|
136
|
+
lines.push(`- [ ] \`${FRAMEWORK_VERSION}\` exists`);
|
|
137
|
+
lines.push("- [ ] Root NODE.md has valid frontmatter (title, owners)");
|
|
138
|
+
lines.push(
|
|
139
|
+
`- [ ] \`${AGENT_INSTRUCTIONS_FILE}\` is the only agent instructions file and has framework markers`,
|
|
140
|
+
);
|
|
141
|
+
lines.push("- [ ] `context-tree verify` passes with no errors");
|
|
142
|
+
lines.push("- [ ] At least one member node exists");
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push("---");
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(
|
|
147
|
+
"**Important:** As you complete each task, check it off in" +
|
|
148
|
+
` \`${INSTALLED_PROGRESS}\` by changing \`- [ ]\` to \`- [x]\`.` +
|
|
149
|
+
" Run `context-tree verify` when done — it will fail if any" +
|
|
150
|
+
" items remain unchecked.",
|
|
151
|
+
);
|
|
152
|
+
lines.push("");
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function writeProgress(repo: Repo, content: string): void {
|
|
157
|
+
const progressPath = join(repo.root, repo.preferredProgressPath());
|
|
158
|
+
mkdirSync(dirname(progressPath), { recursive: true });
|
|
159
|
+
writeFileSync(progressPath, content);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface InitOptions {
|
|
163
|
+
sourceRoot?: string;
|
|
164
|
+
here?: boolean;
|
|
165
|
+
treeName?: string;
|
|
166
|
+
treePath?: string;
|
|
167
|
+
currentCwd?: string;
|
|
168
|
+
gitInitializer?: (root: string) => void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function runInit(repo?: Repo, options?: InitOptions): number {
|
|
172
|
+
const sourceRepo = repo ?? new Repo();
|
|
173
|
+
const initTarget = resolveInitTarget(sourceRepo, options);
|
|
174
|
+
if (initTarget.ok === false) {
|
|
175
|
+
console.error(
|
|
176
|
+
`Error: ${initTarget.message}`,
|
|
177
|
+
);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const r = initTarget.repo;
|
|
181
|
+
const taskListContext = initTarget.dedicatedTreeRepo
|
|
182
|
+
? {
|
|
183
|
+
dedicatedTreeRepo: true,
|
|
184
|
+
sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
|
|
185
|
+
}
|
|
186
|
+
: undefined;
|
|
187
|
+
|
|
188
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
189
|
+
console.log(
|
|
190
|
+
"Recommended workflow: keep the Context Tree in a dedicated repo separate" +
|
|
191
|
+
" from your source/workspace repo.",
|
|
192
|
+
);
|
|
193
|
+
console.log(` Source repo: ${sourceRepo.root}`);
|
|
194
|
+
console.log(` Tree repo: ${r.root}`);
|
|
195
|
+
if (initTarget.createdGitRepo) {
|
|
196
|
+
console.log(" Initialized a new git repo for the tree.");
|
|
197
|
+
}
|
|
198
|
+
console.log();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!r.hasFramework()) {
|
|
202
|
+
try {
|
|
203
|
+
const sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
|
|
204
|
+
console.log(
|
|
205
|
+
"Installing the framework skill bundled with this first-tree package...",
|
|
206
|
+
);
|
|
207
|
+
console.log("Installing skill and scaffolding...");
|
|
208
|
+
installSkill(sourceRoot, r.root);
|
|
209
|
+
renderTemplates(r.root);
|
|
210
|
+
console.log();
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
213
|
+
console.error(`Error: ${message}`);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(ONBOARDING_TEXT);
|
|
219
|
+
console.log("---\n");
|
|
220
|
+
|
|
221
|
+
const groups = evaluateAll(r);
|
|
222
|
+
if (groups.length === 0) {
|
|
223
|
+
console.log("All checks passed. Your context tree is set up.");
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const output = formatTaskList(groups, taskListContext);
|
|
228
|
+
console.log(output);
|
|
229
|
+
writeProgress(r, output);
|
|
230
|
+
console.log(`Progress file written to ${r.preferredProgressPath()}`);
|
|
231
|
+
if (initTarget.dedicatedTreeRepo) {
|
|
232
|
+
console.log(
|
|
233
|
+
`Continue in ${relativePathFrom(sourceRepo.root, r.root)} and keep your source repos available as additional working directories when you populate the tree.`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ParsedInitArgs {
|
|
240
|
+
here?: boolean;
|
|
241
|
+
treeName?: string;
|
|
242
|
+
treePath?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function parseInitArgs(
|
|
246
|
+
args: string[],
|
|
247
|
+
): ParsedInitArgs | { error: string } {
|
|
248
|
+
const parsed: ParsedInitArgs = {};
|
|
249
|
+
|
|
250
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
251
|
+
const arg = args[index];
|
|
252
|
+
switch (arg) {
|
|
253
|
+
case "--here":
|
|
254
|
+
parsed.here = true;
|
|
255
|
+
break;
|
|
256
|
+
case "--tree-name": {
|
|
257
|
+
const value = args[index + 1];
|
|
258
|
+
if (!value) {
|
|
259
|
+
return { error: "Missing value for --tree-name" };
|
|
260
|
+
}
|
|
261
|
+
parsed.treeName = value;
|
|
262
|
+
index += 1;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "--tree-path": {
|
|
266
|
+
const value = args[index + 1];
|
|
267
|
+
if (!value) {
|
|
268
|
+
return { error: "Missing value for --tree-path" };
|
|
269
|
+
}
|
|
270
|
+
parsed.treePath = value;
|
|
271
|
+
index += 1;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
return { error: `Unknown init option: ${arg}` };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (parsed.here && parsed.treeName) {
|
|
280
|
+
return { error: "Cannot combine --here with --tree-name" };
|
|
281
|
+
}
|
|
282
|
+
if (parsed.here && parsed.treePath) {
|
|
283
|
+
return { error: "Cannot combine --here with --tree-path" };
|
|
284
|
+
}
|
|
285
|
+
if (parsed.treeName && parsed.treePath) {
|
|
286
|
+
return { error: "Cannot combine --tree-name with --tree-path" };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return parsed;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function runInitCli(args: string[] = []): number {
|
|
293
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
294
|
+
console.log(INIT_USAGE);
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const parsed = parseInitArgs(args);
|
|
299
|
+
if ("error" in parsed) {
|
|
300
|
+
console.error(parsed.error);
|
|
301
|
+
console.log(INIT_USAGE);
|
|
302
|
+
return 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return runInit(undefined, parsed);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface ResolvedInitTarget {
|
|
309
|
+
ok: true;
|
|
310
|
+
createdGitRepo: boolean;
|
|
311
|
+
dedicatedTreeRepo: boolean;
|
|
312
|
+
repo: Repo;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
interface FailedInitTarget {
|
|
316
|
+
message: string;
|
|
317
|
+
ok: false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function resolveInitTarget(
|
|
321
|
+
sourceRepo: Repo,
|
|
322
|
+
options?: InitOptions,
|
|
323
|
+
): FailedInitTarget | ResolvedInitTarget {
|
|
324
|
+
if (!sourceRepo.isGitRepo()) {
|
|
325
|
+
return {
|
|
326
|
+
ok: false,
|
|
327
|
+
message:
|
|
328
|
+
"not a git repository. Run this from your source/workspace repo, or create a dedicated tree repo first:\n git init\n context-tree init --here",
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const targetRoot = determineTargetRoot(sourceRepo, options);
|
|
333
|
+
const dedicatedTreeRepo = targetRoot !== sourceRepo.root;
|
|
334
|
+
let createdGitRepo = false;
|
|
335
|
+
try {
|
|
336
|
+
createdGitRepo = ensureGitRepo(targetRoot, options?.gitInitializer);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
message,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
createdGitRepo,
|
|
348
|
+
dedicatedTreeRepo,
|
|
349
|
+
repo: new Repo(targetRoot),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function determineTargetRoot(sourceRepo: Repo, options?: InitOptions): string {
|
|
354
|
+
if (options?.treePath) {
|
|
355
|
+
return resolve(options.currentCwd ?? process.cwd(), options.treePath);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (options?.here) {
|
|
359
|
+
return sourceRepo.root;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (options?.treeName) {
|
|
363
|
+
return join(dirname(sourceRepo.root), options.treeName);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (
|
|
367
|
+
sourceRepo.looksLikeTreeRepo()
|
|
368
|
+
|| sourceRepo.isLikelyEmptyRepo()
|
|
369
|
+
|| !sourceRepo.isLikelySourceRepo()
|
|
370
|
+
) {
|
|
371
|
+
return sourceRepo.root;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return join(dirname(sourceRepo.root), `${sourceRepo.repoName()}-context`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function ensureGitRepo(
|
|
378
|
+
targetRoot: string,
|
|
379
|
+
gitInitializer?: (root: string) => void,
|
|
380
|
+
): boolean {
|
|
381
|
+
if (existsSync(targetRoot)) {
|
|
382
|
+
if (!statSync(targetRoot).isDirectory()) {
|
|
383
|
+
throw new Error(`Target path is not a directory: ${targetRoot}`);
|
|
384
|
+
}
|
|
385
|
+
if (new Repo(targetRoot).isGitRepo()) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (readdirSync(targetRoot).length !== 0) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Target path exists and is not a git repository: ${targetRoot}. Run \`git init\` there first or choose a different tree path.`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
(gitInitializer ?? defaultGitInitializer)(targetRoot);
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function defaultGitInitializer(root: string): void {
|
|
402
|
+
execFileSync("git", ["init"], {
|
|
403
|
+
cwd: root,
|
|
404
|
+
stdio: "ignore",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function relativePathFrom(from: string, to: string): string {
|
|
409
|
+
const rel = relative(from, to);
|
|
410
|
+
if (rel === "") {
|
|
411
|
+
return ".";
|
|
412
|
+
}
|
|
413
|
+
return rel.startsWith("..") ? rel : `./${rel}`;
|
|
414
|
+
}
|