@wemuda/launchrail 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/assets/ralph.workflow.js +476 -0
- package/dist/commands/add.d.ts +13 -0
- package/dist/commands/add.js +189 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +44 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.js +171 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/eject.d.ts +17 -0
- package/dist/commands/eject.js +80 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +173 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/smoke.d.ts +21 -0
- package/dist/commands/smoke.js +171 -0
- package/dist/commands/smoke.js.map +1 -0
- package/dist/commands/status.d.ts +15 -0
- package/dist/commands/status.js +91 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +19 -0
- package/dist/commands/sync.js +83 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.js +50 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +127 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/browser-testing.d.ts +16 -0
- package/dist/lib/browser-testing.js +170 -0
- package/dist/lib/browser-testing.js.map +1 -0
- package/dist/lib/checksum.d.ts +1 -0
- package/dist/lib/checksum.js +5 -0
- package/dist/lib/checksum.js.map +1 -0
- package/dist/lib/claudeSettings.d.ts +21 -0
- package/dist/lib/claudeSettings.js +71 -0
- package/dist/lib/claudeSettings.js.map +1 -0
- package/dist/lib/detect.d.ts +23 -0
- package/dist/lib/detect.js +88 -0
- package/dist/lib/detect.js.map +1 -0
- package/dist/lib/diff.d.ts +12 -0
- package/dist/lib/diff.js +99 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/lockfile.d.ts +25 -0
- package/dist/lib/lockfile.js +44 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/manifest.d.ts +41 -0
- package/dist/lib/manifest.js +127 -0
- package/dist/lib/manifest.js.map +1 -0
- package/dist/lib/migrations.d.ts +47 -0
- package/dist/lib/migrations.js +76 -0
- package/dist/lib/migrations.js.map +1 -0
- package/dist/lib/project.d.ts +22 -0
- package/dist/lib/project.js +53 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/ralph.d.ts +12 -0
- package/dist/lib/ralph.js +21 -0
- package/dist/lib/ralph.js.map +1 -0
- package/dist/lib/seeds.d.ts +11 -0
- package/dist/lib/seeds.js +120 -0
- package/dist/lib/seeds.js.map +1 -0
- package/dist/lib/upstream.d.ts +21 -0
- package/dist/lib/upstream.js +37 -0
- package/dist/lib/upstream.js.map +1 -0
- package/dist/lib/writer.d.ts +26 -0
- package/dist/lib/writer.js +62 -0
- package/dist/lib/writer.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BROWSER_TESTING_MODULE } from "../lib/browser-testing.js";
|
|
5
|
+
import { MANIFEST_FILENAME, parseManifest } from "../lib/manifest.js";
|
|
6
|
+
/**
|
|
7
|
+
* Run the project's deterministic verification contract: every configured
|
|
8
|
+
* check, in order, with a pass/fail summary. Agentic smoke testing is separate
|
|
9
|
+
* (`launchrail smoke`) — verify is the stable release gate.
|
|
10
|
+
*/
|
|
11
|
+
export function runVerify(cwd) {
|
|
12
|
+
const manifestPath = join(cwd, MANIFEST_FILENAME);
|
|
13
|
+
if (!existsSync(manifestPath)) {
|
|
14
|
+
console.error(`launchrail: ${MANIFEST_FILENAME} not found — run \`launchrail init\` first.`);
|
|
15
|
+
return { code: 1, results: [] };
|
|
16
|
+
}
|
|
17
|
+
const parsed = parseManifest(readFileSync(manifestPath, "utf8"));
|
|
18
|
+
if (!parsed.manifest) {
|
|
19
|
+
console.error(`launchrail: ${MANIFEST_FILENAME} is invalid:`);
|
|
20
|
+
for (const error of parsed.errors)
|
|
21
|
+
console.error(` - ${error}`);
|
|
22
|
+
return { code: 1, results: [] };
|
|
23
|
+
}
|
|
24
|
+
const { testing, modules } = parsed.manifest;
|
|
25
|
+
const steps = [];
|
|
26
|
+
if (testing.unitCommand)
|
|
27
|
+
steps.push({ name: "unit", command: testing.unitCommand });
|
|
28
|
+
if (modules[BROWSER_TESTING_MODULE] && testing.e2eCommand) {
|
|
29
|
+
steps.push({ name: "e2e", command: testing.e2eCommand });
|
|
30
|
+
}
|
|
31
|
+
if (steps.length === 0) {
|
|
32
|
+
console.error("launchrail: no verification commands configured — set testing.unitCommand (and testing.e2eCommand) in " +
|
|
33
|
+
`${MANIFEST_FILENAME}. An empty verification contract cannot pass.`);
|
|
34
|
+
return { code: 1, results: [] };
|
|
35
|
+
}
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const step of steps) {
|
|
38
|
+
console.log(`\n→ ${step.name}: ${step.command}`);
|
|
39
|
+
const run = spawnSync(step.command, { cwd, shell: true, stdio: "inherit" });
|
|
40
|
+
results.push({ step, status: run.status ?? 1 });
|
|
41
|
+
}
|
|
42
|
+
console.log("");
|
|
43
|
+
for (const { step, status } of results) {
|
|
44
|
+
console.log(` ${status === 0 ? "✓" : "✗"} ${step.name} (${step.command})`);
|
|
45
|
+
}
|
|
46
|
+
const failed = results.filter((r) => r.status !== 0).length;
|
|
47
|
+
console.log(failed === 0 ? "\nVerification passed." : `\nVerification failed: ${failed} of ${results.length} step(s).`);
|
|
48
|
+
return { code: failed === 0 ? 0 : 1, results };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { AVAILABLE_MODULES, runAdd } from "./commands/add.js";
|
|
3
|
+
import { printDiff, runDiff } from "./commands/diff.js";
|
|
4
|
+
import { runDoctor, printDoctor } from "./commands/doctor.js";
|
|
5
|
+
import { runEject } from "./commands/eject.js";
|
|
6
|
+
import { runInit } from "./commands/init.js";
|
|
7
|
+
import { runSmoke } from "./commands/smoke.js";
|
|
8
|
+
import { printStatus, runStatus } from "./commands/status.js";
|
|
9
|
+
import { runSync } from "./commands/sync.js";
|
|
10
|
+
import { runVerify } from "./commands/verify.js";
|
|
11
|
+
import { VERSION } from "./version.js";
|
|
12
|
+
const HELP = `launchrail ${VERSION} — an updatable development system for AI-assisted projects
|
|
13
|
+
|
|
14
|
+
Usage: launchrail <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
init Initialize Launchrail in a new or existing repository
|
|
18
|
+
doctor Validate the repository and environment
|
|
19
|
+
add Add a module to the project (available: ${AVAILABLE_MODULES.join(", ")})
|
|
20
|
+
verify Run the deterministic verification contract
|
|
21
|
+
smoke Scaffold an evidence bundle for an agentic browser smoke run
|
|
22
|
+
status Inspect versions, enabled modules, drift, and missing requirements
|
|
23
|
+
diff Preview upstream changes
|
|
24
|
+
sync Synchronize managed capabilities and run migrations
|
|
25
|
+
eject Stop managing a selected module or file
|
|
26
|
+
promote Inspect potential reusable local improvements
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-h, --help Show this help
|
|
30
|
+
-v, --version Show version
|
|
31
|
+
|
|
32
|
+
init / add options:
|
|
33
|
+
--dry-run Show what would be written without writing
|
|
34
|
+
-y, --yes Accept defaults; no interactive questions
|
|
35
|
+
|
|
36
|
+
smoke options:
|
|
37
|
+
--url <url> Test a specific URL (e.g. a preview environment)
|
|
38
|
+
--dry-run Show what would be scaffolded without writing
|
|
39
|
+
|
|
40
|
+
sync options:
|
|
41
|
+
--dry-run Preview migrations and file updates without writing
|
|
42
|
+
|
|
43
|
+
eject usage:
|
|
44
|
+
launchrail eject <module|file> [--dry-run] Stop managing a module's files or one file
|
|
45
|
+
launchrail eject --all [--dry-run] Vendor mode: eject everything`;
|
|
46
|
+
const NOT_IMPLEMENTED = ["promote"];
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
const command = args[0];
|
|
49
|
+
const flags = new Set(args.slice(1));
|
|
50
|
+
if (command === undefined || command === "-h" || command === "--help" || command === "help") {
|
|
51
|
+
console.log(HELP);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
if (command === "-v" || command === "--version" || command === "version") {
|
|
55
|
+
console.log(VERSION);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
if (command === "init") {
|
|
59
|
+
const outcome = await runInit({
|
|
60
|
+
cwd: process.cwd(),
|
|
61
|
+
dryRun: flags.has("--dry-run"),
|
|
62
|
+
yes: flags.has("--yes") || flags.has("-y"),
|
|
63
|
+
});
|
|
64
|
+
process.exit(outcome.code);
|
|
65
|
+
}
|
|
66
|
+
if (command === "doctor") {
|
|
67
|
+
const outcome = runDoctor(process.cwd());
|
|
68
|
+
printDoctor(outcome);
|
|
69
|
+
process.exit(outcome.code);
|
|
70
|
+
}
|
|
71
|
+
if (command === "add") {
|
|
72
|
+
const module = args[1] !== undefined && !args[1].startsWith("-") ? args[1] : null;
|
|
73
|
+
if (!module) {
|
|
74
|
+
console.error(`launchrail: usage: launchrail add <module> — available modules: ${AVAILABLE_MODULES.join(", ")}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const outcome = await runAdd({
|
|
78
|
+
cwd: process.cwd(),
|
|
79
|
+
module,
|
|
80
|
+
dryRun: flags.has("--dry-run"),
|
|
81
|
+
yes: flags.has("--yes") || flags.has("-y"),
|
|
82
|
+
});
|
|
83
|
+
process.exit(outcome.code);
|
|
84
|
+
}
|
|
85
|
+
if (command === "verify") {
|
|
86
|
+
process.exit(runVerify(process.cwd()).code);
|
|
87
|
+
}
|
|
88
|
+
if (command === "status") {
|
|
89
|
+
const report = runStatus(process.cwd());
|
|
90
|
+
printStatus(report);
|
|
91
|
+
process.exit(report.code);
|
|
92
|
+
}
|
|
93
|
+
if (command === "diff") {
|
|
94
|
+
const outcome = runDiff(process.cwd());
|
|
95
|
+
printDiff(outcome);
|
|
96
|
+
process.exit(outcome.code);
|
|
97
|
+
}
|
|
98
|
+
if (command === "sync") {
|
|
99
|
+
process.exit(runSync({ cwd: process.cwd(), dryRun: flags.has("--dry-run") }).code);
|
|
100
|
+
}
|
|
101
|
+
if (command === "eject") {
|
|
102
|
+
const target = args[1] !== undefined && !args[1].startsWith("-") ? args[1] : null;
|
|
103
|
+
const outcome = runEject({
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
target,
|
|
106
|
+
all: flags.has("--all"),
|
|
107
|
+
dryRun: flags.has("--dry-run"),
|
|
108
|
+
});
|
|
109
|
+
process.exit(outcome.code);
|
|
110
|
+
}
|
|
111
|
+
if (command === "smoke") {
|
|
112
|
+
const urlIndex = args.indexOf("--url");
|
|
113
|
+
const outcome = await runSmoke({
|
|
114
|
+
cwd: process.cwd(),
|
|
115
|
+
url: urlIndex !== -1 ? (args[urlIndex + 1] ?? null) : null,
|
|
116
|
+
dryRun: flags.has("--dry-run"),
|
|
117
|
+
});
|
|
118
|
+
process.exit(outcome.code);
|
|
119
|
+
}
|
|
120
|
+
if (NOT_IMPLEMENTED.includes(command)) {
|
|
121
|
+
console.error(`launchrail: "${command}" is not implemented yet.`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
console.error(`launchrail: unknown command "${command}"\n`);
|
|
125
|
+
console.error(HELP);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RepoDetection } from "./detect.js";
|
|
2
|
+
import type { Manifest } from "./manifest.js";
|
|
3
|
+
import type { FileSpec } from "./writer.js";
|
|
4
|
+
export declare const BROWSER_TESTING_MODULE = "browser-testing";
|
|
5
|
+
export declare const SMOKE_JOURNEYS_PATH = "docs/testing/smoke-journeys.md";
|
|
6
|
+
export declare const SEMANTIC_SCRIPTS: readonly ["setup", "dev", "verify", "smoke", "doctor"];
|
|
7
|
+
export interface BrowserTestingContext {
|
|
8
|
+
manifest: Manifest;
|
|
9
|
+
detection: RepoDetection;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Everything `launchrail add browser-testing` seeds. All files are seeded-class:
|
|
13
|
+
* created once, then project-owned. An existing Playwright config means the
|
|
14
|
+
* project already has an E2E layer — keep it, seed neither config nor baseline.
|
|
15
|
+
*/
|
|
16
|
+
export declare function browserTestingFiles(ctx: BrowserTestingContext): FileSpec[];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export const BROWSER_TESTING_MODULE = "browser-testing";
|
|
2
|
+
export const SMOKE_JOURNEYS_PATH = "docs/testing/smoke-journeys.md";
|
|
3
|
+
export const SEMANTIC_SCRIPTS = ["setup", "dev", "verify", "smoke", "doctor"];
|
|
4
|
+
const DEFAULT_APP_URL = "http://localhost:3000";
|
|
5
|
+
function playwrightConfig(manifest) {
|
|
6
|
+
const appUrl = manifest.testing.appUrl ?? DEFAULT_APP_URL;
|
|
7
|
+
const webServer = manifest.testing.devCommand
|
|
8
|
+
? ` webServer: {
|
|
9
|
+
command: ${JSON.stringify(manifest.testing.devCommand)},
|
|
10
|
+
url: baseURL,
|
|
11
|
+
reuseExistingServer: !process.env.CI,
|
|
12
|
+
},
|
|
13
|
+
`
|
|
14
|
+
: ` // webServer: { command: "npm run dev", url: baseURL, reuseExistingServer: !process.env.CI },
|
|
15
|
+
`;
|
|
16
|
+
return `import { defineConfig } from "@playwright/test";
|
|
17
|
+
|
|
18
|
+
// Seeded by Launchrail — this file is yours to adapt.
|
|
19
|
+
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? ${JSON.stringify(appUrl)};
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
testDir: "tests/e2e",
|
|
23
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
24
|
+
use: {
|
|
25
|
+
baseURL,
|
|
26
|
+
trace: "retain-on-failure",
|
|
27
|
+
screenshot: "only-on-failure",
|
|
28
|
+
},
|
|
29
|
+
${webServer}});
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
function smokeSpec() {
|
|
33
|
+
return `import { expect, test } from "@playwright/test";
|
|
34
|
+
|
|
35
|
+
// Seeded by Launchrail — the E2E baseline: the app starts, responds, and renders
|
|
36
|
+
// without console errors. Extend or replace with real journeys as they stabilize.
|
|
37
|
+
test("app starts and renders without console errors", async ({ page }) => {
|
|
38
|
+
const errors: string[] = [];
|
|
39
|
+
page.on("pageerror", (error) => errors.push(String(error)));
|
|
40
|
+
page.on("console", (message) => {
|
|
41
|
+
if (message.type() === "error") errors.push(message.text());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const response = await page.goto("/");
|
|
45
|
+
expect(response?.ok(), "app must respond successfully on baseURL").toBe(true);
|
|
46
|
+
await expect(page.locator("body")).toBeVisible();
|
|
47
|
+
expect(errors).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function smokeJourneysDoc() {
|
|
52
|
+
return `# Smoke journeys
|
|
53
|
+
|
|
54
|
+
Seeded by Launchrail — this file is yours. It is the smoke-journey contract: the
|
|
55
|
+
concrete user journeys an agent drives through the real app in a browser before
|
|
56
|
+
user-facing work counts as done.
|
|
57
|
+
|
|
58
|
+
- Every ticket that changes user-facing behavior adds or updates a journey here
|
|
59
|
+
(or defines one in the ticket itself).
|
|
60
|
+
- Each journey heading starts with \`## Journey:\` so tools can list them —
|
|
61
|
+
\`npx @wemuda/launchrail smoke\` scaffolds an evidence bundle from this file.
|
|
62
|
+
- Agentic smoke testing supplements deterministic tests; it never replaces them.
|
|
63
|
+
When a smoke run finds a real bug: record the reproduction, add a failing
|
|
64
|
+
deterministic test, fix the bug, prove the test passes, re-run the journey.
|
|
65
|
+
|
|
66
|
+
Every journey is verified against the standard checks plus its own list:
|
|
67
|
+
|
|
68
|
+
- No uncaught console errors
|
|
69
|
+
- No failed API requests
|
|
70
|
+
- The success state is visible
|
|
71
|
+
- Data remains after refresh
|
|
72
|
+
|
|
73
|
+
## Journey: App loads
|
|
74
|
+
|
|
75
|
+
- startAt: /
|
|
76
|
+
- actor: anonymous
|
|
77
|
+
|
|
78
|
+
Steps:
|
|
79
|
+
|
|
80
|
+
1. Open the app root.
|
|
81
|
+
2. Confirm the main content renders.
|
|
82
|
+
|
|
83
|
+
Verify:
|
|
84
|
+
|
|
85
|
+
- The page shows meaningful content, not an error screen.
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
const ADD_PLAYWRIGHT_DEP = {
|
|
89
|
+
pnpm: "pnpm add -D @playwright/test",
|
|
90
|
+
yarn: "yarn add -D @playwright/test",
|
|
91
|
+
bun: "bun add -d @playwright/test",
|
|
92
|
+
npm: "npm install -D @playwright/test",
|
|
93
|
+
};
|
|
94
|
+
function setupScript(pm) {
|
|
95
|
+
return `#!/usr/bin/env node
|
|
96
|
+
// Seeded by Launchrail — yours to adapt. Prepares a fresh clone for local, CI, or cloud work.
|
|
97
|
+
import { spawnSync } from "node:child_process";
|
|
98
|
+
import { existsSync } from "node:fs";
|
|
99
|
+
|
|
100
|
+
const cloud = process.argv.includes("--cloud") || process.env.CLAUDE_CODE_REMOTE === "true" || !!process.env.CI;
|
|
101
|
+
|
|
102
|
+
function run(command) {
|
|
103
|
+
console.log("\\n$ " + command);
|
|
104
|
+
const result = spawnSync(command, { shell: true, stdio: "inherit" });
|
|
105
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
run("${pm} install");
|
|
109
|
+
if (!existsSync("node_modules/@playwright/test")) {
|
|
110
|
+
run("${ADD_PLAYWRIGHT_DEP[pm]}");
|
|
111
|
+
}
|
|
112
|
+
// Cloud and CI machines need browser OS dependencies; local machines usually have them.
|
|
113
|
+
run(cloud ? "npx playwright install --with-deps chromium" : "npx playwright install chromium");
|
|
114
|
+
console.log("\\nSetup complete. Next: node scripts/doctor.mjs");
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
function devScript(devCommand) {
|
|
118
|
+
return `#!/usr/bin/env node
|
|
119
|
+
// Seeded by Launchrail — yours to adapt. Starts the app for development and testing.
|
|
120
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
121
|
+
import { mkdirSync, openSync } from "node:fs";
|
|
122
|
+
|
|
123
|
+
const DEV_COMMAND = ${JSON.stringify(devCommand ?? "")};
|
|
124
|
+
if (DEV_COMMAND === "") {
|
|
125
|
+
console.error("No dev command configured. Edit scripts/dev.mjs and set DEV_COMMAND to whatever starts this app.");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (process.argv.includes("--background")) {
|
|
130
|
+
// Cloud and CI sessions need the app running without holding the terminal.
|
|
131
|
+
mkdirSync(".launchrail/state", { recursive: true });
|
|
132
|
+
const log = openSync(".launchrail/state/dev.log", "a");
|
|
133
|
+
const child = spawn(DEV_COMMAND, { shell: true, detached: true, stdio: ["ignore", log, log] });
|
|
134
|
+
child.unref();
|
|
135
|
+
console.log("Started \`" + DEV_COMMAND + "\` in the background (pid " + child.pid + "). Logs: .launchrail/state/dev.log");
|
|
136
|
+
} else {
|
|
137
|
+
const result = spawnSync(DEV_COMMAND, { shell: true, stdio: "inherit" });
|
|
138
|
+
process.exit(result.status ?? 0);
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
/** Stable semantic entry point that delegates to the Launchrail CLI. */
|
|
143
|
+
function delegatingScript(subcommand) {
|
|
144
|
+
return `#!/usr/bin/env node
|
|
145
|
+
// Seeded by Launchrail — stable entry point for humans, CI, and agents; delegates to the Launchrail CLI.
|
|
146
|
+
import { spawnSync } from "node:child_process";
|
|
147
|
+
|
|
148
|
+
const result = spawnSync(
|
|
149
|
+
"npx",
|
|
150
|
+
["@wemuda/launchrail", "${subcommand}", ...process.argv.slice(2)],
|
|
151
|
+
{ shell: process.platform === "win32", stdio: "inherit" },
|
|
152
|
+
);
|
|
153
|
+
process.exit(result.status ?? 1);
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Everything `launchrail add browser-testing` seeds. All files are seeded-class:
|
|
158
|
+
* created once, then project-owned. An existing Playwright config means the
|
|
159
|
+
* project already has an E2E layer — keep it, seed neither config nor baseline.
|
|
160
|
+
*/
|
|
161
|
+
export function browserTestingFiles(ctx) {
|
|
162
|
+
const pm = ctx.detection.packageManager ?? "npm";
|
|
163
|
+
const specs = [];
|
|
164
|
+
if (!ctx.detection.playwrightConfigFile) {
|
|
165
|
+
specs.push({ relPath: "playwright.config.ts", content: playwrightConfig(ctx.manifest), ownership: "seeded" }, { relPath: "tests/e2e/smoke.spec.ts", content: smokeSpec(), ownership: "seeded" });
|
|
166
|
+
}
|
|
167
|
+
specs.push({ relPath: SMOKE_JOURNEYS_PATH, content: smokeJourneysDoc(), ownership: "seeded" }, { relPath: "scripts/setup.mjs", content: setupScript(pm), ownership: "seeded", executable: true }, { relPath: "scripts/dev.mjs", content: devScript(ctx.manifest.testing.devCommand), ownership: "seeded", executable: true }, { relPath: "scripts/verify.mjs", content: delegatingScript("verify"), ownership: "seeded", executable: true }, { relPath: "scripts/smoke.mjs", content: delegatingScript("smoke"), ownership: "seeded", executable: true }, { relPath: "scripts/doctor.mjs", content: delegatingScript("doctor"), ownership: "seeded", executable: true });
|
|
168
|
+
return specs;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=browser-testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-testing.js","sourceRoot":"","sources":["../../src/lib/browser-testing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sha256(content: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checksum.js","sourceRoot":"","sources":["../../src/lib/checksum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,MAAM,CAAC,OAAe;IACpC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consuming projects subscribe to the Launchrail plugin through a committed,
|
|
3
|
+
* project-scoped declaration in .claude/settings.json (ADR-0003). The file is
|
|
4
|
+
* project-owned and shared with unrelated Claude Code configuration, so it is
|
|
5
|
+
* never tracked in the lockfile and never replaced: init only performs an
|
|
6
|
+
* additive merge of the two keys below, and an explicit opt-out
|
|
7
|
+
* (`"launchrail@launchrail": false`) is respected.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CLAUDE_SETTINGS_PATH: string;
|
|
10
|
+
export type DeclarationState = "declared" | "no-file" | "invalid-json" | "undeclared";
|
|
11
|
+
export declare function declarationState(root: string): DeclarationState;
|
|
12
|
+
export type SettingsPlanKind = "create" | "merge" | "skip-declared" | "skip-invalid";
|
|
13
|
+
export interface SettingsPlan {
|
|
14
|
+
kind: SettingsPlanKind;
|
|
15
|
+
detail: string;
|
|
16
|
+
/** Full file content to write; null when nothing should change. */
|
|
17
|
+
content: string | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function planPluginDeclaration(root: string): SettingsPlan;
|
|
20
|
+
/** Execute a plan. Returns true when the file was written. */
|
|
21
|
+
export declare function applyPluginDeclaration(root: string, plan: SettingsPlan): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Consuming projects subscribe to the Launchrail plugin through a committed,
|
|
5
|
+
* project-scoped declaration in .claude/settings.json (ADR-0003). The file is
|
|
6
|
+
* project-owned and shared with unrelated Claude Code configuration, so it is
|
|
7
|
+
* never tracked in the lockfile and never replaced: init only performs an
|
|
8
|
+
* additive merge of the two keys below, and an explicit opt-out
|
|
9
|
+
* (`"launchrail@launchrail": false`) is respected.
|
|
10
|
+
*/
|
|
11
|
+
export const CLAUDE_SETTINGS_PATH = join(".claude", "settings.json");
|
|
12
|
+
const MARKETPLACE_NAME = "launchrail";
|
|
13
|
+
const PLUGIN_KEY = "launchrail@launchrail";
|
|
14
|
+
const MARKETPLACE_SOURCE = { source: "github", repo: "wemuda/launchrail" };
|
|
15
|
+
function readSettings(root) {
|
|
16
|
+
const path = join(root, CLAUDE_SETTINGS_PATH);
|
|
17
|
+
if (!existsSync(path))
|
|
18
|
+
return { settings: null, state: "no-file" };
|
|
19
|
+
let data;
|
|
20
|
+
try {
|
|
21
|
+
data = JSON.parse(readFileSync(path, "utf8"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { settings: null, state: "invalid-json" };
|
|
25
|
+
}
|
|
26
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
27
|
+
return { settings: null, state: "invalid-json" };
|
|
28
|
+
}
|
|
29
|
+
const settings = data;
|
|
30
|
+
const declared = settings.extraKnownMarketplaces?.[MARKETPLACE_NAME] !== undefined &&
|
|
31
|
+
settings.enabledPlugins?.[PLUGIN_KEY] !== undefined;
|
|
32
|
+
return { settings, state: declared ? "declared" : "undeclared" };
|
|
33
|
+
}
|
|
34
|
+
export function declarationState(root) {
|
|
35
|
+
return readSettings(root).state;
|
|
36
|
+
}
|
|
37
|
+
export function planPluginDeclaration(root) {
|
|
38
|
+
const { settings, state } = readSettings(root);
|
|
39
|
+
if (state === "invalid-json") {
|
|
40
|
+
return { kind: "skip-invalid", detail: "unparseable JSON — not touching it", content: null };
|
|
41
|
+
}
|
|
42
|
+
if (state === "declared") {
|
|
43
|
+
return { kind: "skip-declared", detail: "plugin already declared", content: null };
|
|
44
|
+
}
|
|
45
|
+
const merged = settings ? { ...settings } : {};
|
|
46
|
+
merged.extraKnownMarketplaces = { ...merged.extraKnownMarketplaces };
|
|
47
|
+
if (merged.extraKnownMarketplaces[MARKETPLACE_NAME] === undefined) {
|
|
48
|
+
merged.extraKnownMarketplaces[MARKETPLACE_NAME] = { source: MARKETPLACE_SOURCE };
|
|
49
|
+
}
|
|
50
|
+
merged.enabledPlugins = { ...merged.enabledPlugins };
|
|
51
|
+
if (merged.enabledPlugins[PLUGIN_KEY] === undefined) {
|
|
52
|
+
merged.enabledPlugins[PLUGIN_KEY] = true;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
kind: state === "no-file" ? "create" : "merge",
|
|
56
|
+
detail: state === "no-file"
|
|
57
|
+
? "declares the Launchrail plugin for this project"
|
|
58
|
+
: "adding the Launchrail plugin declaration, keeping existing settings",
|
|
59
|
+
content: JSON.stringify(merged, null, 2) + "\n",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Execute a plan. Returns true when the file was written. */
|
|
63
|
+
export function applyPluginDeclaration(root, plan) {
|
|
64
|
+
if (plan.content === null)
|
|
65
|
+
return false;
|
|
66
|
+
const abs = join(root, CLAUDE_SETTINGS_PATH);
|
|
67
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
68
|
+
writeFileSync(abs, plan.content, "utf8");
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=claudeSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeSettings.js","sourceRoot":"","sources":["../../src/lib/claudeSettings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAErE,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAC3C,MAAM,kBAAkB,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;AAU3E,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACnE,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,QAAQ,GAAG,IAAgB,CAAC;IAClC,MAAM,QAAQ,GACZ,QAAQ,CAAC,sBAAsB,EAAE,CAAC,gBAAgB,CAAC,KAAK,SAAS;QACjE,QAAQ,CAAC,cAAc,EAAE,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC;IACtD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;AAClC,CAAC;AAWD,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,oCAAoC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC/F,CAAC;IACD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,yBAAyB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACrF,CAAC;IAED,MAAM,MAAM,GAAa,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,MAAM,CAAC,sBAAsB,GAAG,EAAE,GAAG,MAAM,CAAC,sBAAsB,EAAE,CAAC;IACrE,IAAI,MAAM,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,KAAK,SAAS,EAAE,CAAC;QAClE,MAAM,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACnF,CAAC;IACD,MAAM,CAAC,cAAc,GAAG,EAAE,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrD,IAAI,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,SAAS,EAAE,CAAC;QACpD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;QAC9C,MAAM,EACJ,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,iDAAiD;YACnD,CAAC,CAAC,qEAAqE;QAC3E,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI;KAChD,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,IAAkB;IACrE,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC7C,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
|
|
2
|
+
export interface RepoDetection {
|
|
3
|
+
isGitRepo: boolean;
|
|
4
|
+
gitRemoteUrl: string | null;
|
|
5
|
+
/** Share of the last 20 commit subjects following Conventional Commits; null when there is no history. */
|
|
6
|
+
conventionalCommitRatio: number | null;
|
|
7
|
+
packageManager: PackageManager | null;
|
|
8
|
+
hasPackageJson: boolean;
|
|
9
|
+
packageName: string | null;
|
|
10
|
+
testScript: string | null;
|
|
11
|
+
devScript: string | null;
|
|
12
|
+
/** `@playwright/test` (or `playwright`) declared in dependencies/devDependencies. */
|
|
13
|
+
hasPlaywrightDep: boolean;
|
|
14
|
+
/** First `playwright.config.*` found at the repo root, if any. */
|
|
15
|
+
playwrightConfigFile: string | null;
|
|
16
|
+
projectName: string;
|
|
17
|
+
hasAgentsMd: boolean;
|
|
18
|
+
hasClaudeMd: boolean;
|
|
19
|
+
hasManifest: boolean;
|
|
20
|
+
/** docs/agents/ is the output location of Matt Pocock's /setup-matt-pocock-skills. */
|
|
21
|
+
hasMattPocockSetup: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function detectRepo(root: string): RepoDetection;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { MANIFEST_FILENAME } from "./manifest.js";
|
|
5
|
+
function git(root, args) {
|
|
6
|
+
try {
|
|
7
|
+
return execFileSync("git", args, {
|
|
8
|
+
cwd: root,
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
11
|
+
}).trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const CONVENTIONAL_SUBJECT = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]*\))?!?:\s/;
|
|
18
|
+
export function detectRepo(root) {
|
|
19
|
+
const isGitRepo = git(root, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
20
|
+
const gitRemoteUrl = isGitRepo ? git(root, ["remote", "get-url", "origin"]) : null;
|
|
21
|
+
let conventionalCommitRatio = null;
|
|
22
|
+
if (isGitRepo) {
|
|
23
|
+
const log = git(root, ["log", "-n", "20", "--format=%s"]);
|
|
24
|
+
const subjects = log ? log.split("\n").filter(Boolean) : [];
|
|
25
|
+
if (subjects.length > 0) {
|
|
26
|
+
conventionalCommitRatio = subjects.filter((s) => CONVENTIONAL_SUBJECT.test(s)).length / subjects.length;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const hasPackageJson = existsSync(join(root, "package.json"));
|
|
30
|
+
let packageName = null;
|
|
31
|
+
let testScript = null;
|
|
32
|
+
let devScript = null;
|
|
33
|
+
let hasPlaywrightDep = false;
|
|
34
|
+
let packageManagerField = null;
|
|
35
|
+
if (hasPackageJson) {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
38
|
+
if (typeof pkg.name === "string")
|
|
39
|
+
packageName = pkg.name;
|
|
40
|
+
if (typeof pkg.scripts?.test === "string")
|
|
41
|
+
testScript = pkg.scripts.test;
|
|
42
|
+
if (typeof pkg.scripts?.dev === "string")
|
|
43
|
+
devScript = pkg.scripts.dev;
|
|
44
|
+
if (typeof pkg.packageManager === "string")
|
|
45
|
+
packageManagerField = pkg.packageManager;
|
|
46
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
47
|
+
hasPlaywrightDep = "@playwright/test" in deps || "playwright" in deps;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Malformed package.json is doctor's problem, not detection's.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const playwrightConfigFile = ["ts", "js", "mjs", "cjs", "mts", "cts"]
|
|
54
|
+
.map((ext) => `playwright.config.${ext}`)
|
|
55
|
+
.find((file) => existsSync(join(root, file))) ?? null;
|
|
56
|
+
let packageManager = null;
|
|
57
|
+
if (existsSync(join(root, "pnpm-lock.yaml")))
|
|
58
|
+
packageManager = "pnpm";
|
|
59
|
+
else if (existsSync(join(root, "yarn.lock")))
|
|
60
|
+
packageManager = "yarn";
|
|
61
|
+
else if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")))
|
|
62
|
+
packageManager = "bun";
|
|
63
|
+
else if (existsSync(join(root, "package-lock.json")))
|
|
64
|
+
packageManager = "npm";
|
|
65
|
+
else if (packageManagerField) {
|
|
66
|
+
const prefix = packageManagerField.split("@")[0];
|
|
67
|
+
if (prefix === "pnpm" || prefix === "yarn" || prefix === "bun" || prefix === "npm")
|
|
68
|
+
packageManager = prefix;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
isGitRepo,
|
|
72
|
+
gitRemoteUrl,
|
|
73
|
+
conventionalCommitRatio,
|
|
74
|
+
packageManager,
|
|
75
|
+
hasPackageJson,
|
|
76
|
+
packageName,
|
|
77
|
+
testScript,
|
|
78
|
+
devScript,
|
|
79
|
+
hasPlaywrightDep,
|
|
80
|
+
playwrightConfigFile,
|
|
81
|
+
projectName: packageName ?? basename(root),
|
|
82
|
+
hasAgentsMd: existsSync(join(root, "AGENTS.md")),
|
|
83
|
+
hasClaudeMd: existsSync(join(root, "CLAUDE.md")),
|
|
84
|
+
hasManifest: existsSync(join(root, MANIFEST_FILENAME)),
|
|
85
|
+
hasMattPocockSetup: existsSync(join(root, "docs", "agents")),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/lib/detect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal line-based diff — enough for `launchrail diff` to preview upstream
|
|
3
|
+
* changes without a dependency. LCS is quadratic, which is fine at the size
|
|
4
|
+
* of the files Launchrail manages.
|
|
5
|
+
*/
|
|
6
|
+
export interface DiffOp {
|
|
7
|
+
kind: "context" | "del" | "add";
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function diffOps(oldText: string, newText: string): DiffOp[];
|
|
11
|
+
/** Unified-diff hunks (no ---/+++ header). Empty string when the texts match. */
|
|
12
|
+
export declare function formatUnifiedDiff(oldText: string, newText: string, context?: number): string;
|