@kraken-e2e/cli 0.1.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 +674 -0
- package/bin/run.js +4 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +24 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/plugins/install.d.ts +13 -0
- package/dist/commands/plugins/install.d.ts.map +1 -0
- package/dist/commands/plugins/install.js +30 -0
- package/dist/commands/plugins/install.js.map +1 -0
- package/dist/commands/run.d.ts +13 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +38 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +11 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +31 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/doctor-report.d.ts +5 -0
- package/dist/doctor-report.d.ts.map +1 -0
- package/dist/doctor-report.js +54 -0
- package/dist/doctor-report.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/init-project.d.ts +9 -0
- package/dist/init-project.d.ts.map +1 -0
- package/dist/init-project.js +77 -0
- package/dist/init-project.js.map +1 -0
- package/dist/plugins-install.d.ts +17 -0
- package/dist/plugins-install.d.ts.map +1 -0
- package/dist/plugins-install.js +148 -0
- package/dist/plugins-install.js.map +1 -0
- package/dist/run-project.d.ts +17 -0
- package/dist/run-project.d.ts.map +1 -0
- package/dist/run-project.js +136 -0
- package/dist/run-project.js.map +1 -0
- package/dist/serve.d.ts +14 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +182 -0
- package/dist/serve.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Doctor extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static flags: {
|
|
7
|
+
cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAG7C,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,OAAO;IACzC,OAAgB,WAAW,SAC6E;IAExG,OAAgB,QAAQ,WAAmE;IAE3F,OAAgB,cAAc,UAAQ;IAEtC,OAAgB,KAAK;;MAEnB;IAEI,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAW9B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { renderDoctorText } from '@kraken-e2e/doctor';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import { buildDoctorReport } from '../doctor-report.js';
|
|
4
|
+
export default class Doctor extends Command {
|
|
5
|
+
static description = 'Diagnose the environment: host platform, toolchain, and per-driver readiness with actionable fixes.';
|
|
6
|
+
static examples = ['<%= config.bin %> doctor', '<%= config.bin %> doctor --json'];
|
|
7
|
+
static enableJsonFlag = true;
|
|
8
|
+
static flags = {
|
|
9
|
+
cwd: Flags.string({ description: 'project directory (defaults to the current one)' }),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(Doctor);
|
|
13
|
+
const report = await buildDoctorReport({
|
|
14
|
+
...(flags.cwd !== undefined ? { cwd: flags.cwd } : {}),
|
|
15
|
+
});
|
|
16
|
+
if (!this.jsonEnabled()) {
|
|
17
|
+
this.log(renderDoctorText(report));
|
|
18
|
+
if (report.summary.fail > 0)
|
|
19
|
+
this.exit(1);
|
|
20
|
+
}
|
|
21
|
+
return report;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,OAAO;IACzC,MAAM,CAAU,WAAW,GACzB,qGAAqG,CAAC;IAExG,MAAM,CAAU,QAAQ,GAAG,CAAC,0BAA0B,EAAE,iCAAiC,CAAC,CAAC;IAE3F,MAAM,CAAU,cAAc,GAAG,IAAI,CAAC;IAEtC,MAAM,CAAU,KAAK,GAAG;QACtB,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,iDAAiD,EAAE,CAAC;KACtF,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;YACnC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAItC,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,OAAO;IACvC,OAAgB,WAAW,SAC8D;IAEzF,OAAgB,QAAQ,WAA8B;IAEhD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAI3B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { initProject } from '../init-project.js';
|
|
3
|
+
export default class Init extends Command {
|
|
4
|
+
static description = 'Scaffold a Kraken test project (config, steps, features, VS Code Cucumber settings).';
|
|
5
|
+
static examples = ['<%= config.bin %> init'];
|
|
6
|
+
async run() {
|
|
7
|
+
const code = initProject({ cwd: process.cwd(), write: (line) => this.log(line) });
|
|
8
|
+
if (code !== 0)
|
|
9
|
+
this.exit(code);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,OAAO;IACvC,MAAM,CAAU,WAAW,GACzB,sFAAsF,CAAC;IAEzF,MAAM,CAAU,QAAQ,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAEtD,KAAK,CAAC,GAAG;QACP,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClF,IAAI,IAAI,KAAK,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class PluginsInstall extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
package: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
'skip-install': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/commands/plugins/install.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,OAAO,EAAS,MAAM,aAAa,CAAC;AAGnD,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,OAAO;IACjD,OAAgB,WAAW,SAC+E;IAE1G,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAU3B"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { systemHostProbe } from '@kraken-e2e/core';
|
|
2
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
3
|
+
import { installPlugin } from '../../plugins-install.js';
|
|
4
|
+
export default class PluginsInstall extends Command {
|
|
5
|
+
static description = 'Install a Kraken driver as an exact-pinned project devDependency and register it in kraken.config.ts.';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> plugins install @kraken-e2e/driver-ios',
|
|
8
|
+
'<%= config.bin %> plugins:install @kraken-e2e/driver-android',
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
package: Args.string({ description: 'driver package name', required: true }),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
'skip-install': Flags.boolean({
|
|
15
|
+
description: 'validate and register an already-installed package only',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(PluginsInstall);
|
|
20
|
+
const result = await installPlugin({
|
|
21
|
+
packageName: args.package,
|
|
22
|
+
skipInstall: flags['skip-install'],
|
|
23
|
+
host: systemHostProbe.detect(),
|
|
24
|
+
write: (line) => this.log(line),
|
|
25
|
+
});
|
|
26
|
+
if (result.exitCode !== 0)
|
|
27
|
+
this.exit(result.exitCode);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/commands/plugins/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,OAAO;IACjD,MAAM,CAAU,WAAW,GACzB,uGAAuG,CAAC;IAE1G,MAAM,CAAU,QAAQ,GAAG;QACzB,0DAA0D;QAC1D,8DAA8D;KAC/D,CAAC;IAEF,MAAM,CAAU,IAAI,GAAG;QACrB,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KAC7E,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC;YAC5B,WAAW,EAAE,yDAAyD;SACvE,CAAC;KACH,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YACjC,WAAW,EAAE,IAAI,CAAC,OAAO;YACzB,WAAW,EAAE,KAAK,CAAC,cAAc,CAAC;YAClC,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE;YAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;SAChC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Run extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
tags: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
plain: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAI7C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,OAAgB,WAAW,SAC6D;IAExF,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;;MASnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB3B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { runProject } from '../run-project.js';
|
|
3
|
+
export default class Run extends Command {
|
|
4
|
+
static description = 'Compile the feature files (dry-run analysis) and execute the multi-actor scenarios.';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> run',
|
|
7
|
+
'<%= config.bin %> run --dry-run',
|
|
8
|
+
'<%= config.bin %> run --tags "@smoke and not @wip"',
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
config: Flags.string({ char: 'c', description: 'path to kraken.config.ts' }),
|
|
12
|
+
tags: Flags.string({ char: 't', description: 'tag expression, e.g. "@smoke and not @wip"' }),
|
|
13
|
+
'dry-run': Flags.boolean({
|
|
14
|
+
description: 'compile and statically analyze only — boot no sessions',
|
|
15
|
+
}),
|
|
16
|
+
plain: Flags.boolean({
|
|
17
|
+
description: 'force the plain line renderer (no live Ink UI)',
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(Run);
|
|
22
|
+
const result = await runProject({
|
|
23
|
+
...(flags.config !== undefined ? { configPath: flags.config } : {}),
|
|
24
|
+
...(flags.tags !== undefined ? { tags: flags.tags } : {}),
|
|
25
|
+
dryRun: flags['dry-run'],
|
|
26
|
+
plain: flags.plain,
|
|
27
|
+
write: (line) => this.log(line),
|
|
28
|
+
});
|
|
29
|
+
// ALWAYS exit explicitly: embedded Appium/WDIO leave keep-alive handles
|
|
30
|
+
// that keep the event loop from draining after a successful run
|
|
31
|
+
// (observed live: `kraken run` hung post-runFinished — ADR-0005).
|
|
32
|
+
// Drain piped stdout first: process.exit does not wait for pending writes
|
|
33
|
+
// (the CI `kraken run | tee` case would truncate the failure summary).
|
|
34
|
+
await new Promise((resolve) => process.stdout.write('', () => resolve()));
|
|
35
|
+
this.exit(result.exitCode);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,MAAM,CAAU,WAAW,GACzB,qFAAqF,CAAC;IAExF,MAAM,CAAU,QAAQ,GAAG;QACzB,uBAAuB;QACvB,iCAAiC;QACjC,oDAAoD;KACrD,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;QAC5E,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAC;QAC5F,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC;YACvB,WAAW,EAAE,wDAAwD;SACtE,CAAC;QACF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACnB,WAAW,EAAE,gDAAgD;SAC9D,CAAC;KACH,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;SAChC,CAAC,CAAC;QACH,wEAAwE;QACxE,gEAAgE;QAChE,kEAAkE;QAClE,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Serve extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
port: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
host: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAI7C,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,OAAO;IACxC,OAAgB,WAAW,SACuE;IAElG,OAAgB,QAAQ,WAAsE;IAE9F,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB3B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { findConfigPath } from '@kraken-e2e/config';
|
|
3
|
+
import { Command, Flags } from '@oclif/core';
|
|
4
|
+
import { startServe } from '../serve.js';
|
|
5
|
+
export default class Serve extends Command {
|
|
6
|
+
static description = 'Serve run results (event streams + artifacts) over HTTP/WebSocket — the GUI-ready projection.';
|
|
7
|
+
static examples = ['<%= config.bin %> serve', '<%= config.bin %> serve --port 4000'];
|
|
8
|
+
static flags = {
|
|
9
|
+
port: Flags.integer({ description: 'port (default: OS-assigned)', default: 0 }),
|
|
10
|
+
host: Flags.string({ description: 'bind host', default: '127.0.0.1' }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(Serve);
|
|
14
|
+
const configPath = findConfigPath(process.cwd());
|
|
15
|
+
const projectRoot = configPath ? join(configPath, '..') : process.cwd();
|
|
16
|
+
const handle = await startServe({
|
|
17
|
+
runsDir: join(projectRoot, '.kraken', 'runs'),
|
|
18
|
+
port: flags.port,
|
|
19
|
+
host: flags.host,
|
|
20
|
+
log: (line) => this.log(line),
|
|
21
|
+
});
|
|
22
|
+
this.log(`Viewer: ${handle.url} — Ctrl-C to stop.`);
|
|
23
|
+
// Serve runs until interrupted; the CLI owns SIGINT (ADR-0005).
|
|
24
|
+
await new Promise((resolve) => {
|
|
25
|
+
process.once('SIGINT', () => {
|
|
26
|
+
void handle.close().then(resolve);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,OAAO;IACxC,MAAM,CAAU,WAAW,GACzB,+FAA+F,CAAC;IAElG,MAAM,CAAU,QAAQ,GAAG,CAAC,yBAAyB,EAAE,qCAAqC,CAAC,CAAC;IAE9F,MAAM,CAAU,KAAK,GAAG;QACtB,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,6BAA6B,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAC/E,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;KACvE,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC;YAC7C,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;SAC9B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,GAAG,oBAAoB,CAAC,CAAC;QACpD,gEAAgE;QAChE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;gBAC1B,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-report.d.ts","sourceRoot":"","sources":["../src/doctor-report.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,oBAAoB,CAAC;AAyB5B,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAqBxF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `kraken doctor` pipeline (ADR-0001 §5.13 composition): the CLI — the
|
|
3
|
+
* composition layer — injects HostContext (from core's probe), driver gate
|
|
4
|
+
* statuses (from core's registry), and driver-contributed checks into the
|
|
5
|
+
* pure @kraken-e2e/doctor engine.
|
|
6
|
+
*/
|
|
7
|
+
import { loadConfig } from '@kraken-e2e/config';
|
|
8
|
+
import { createHostContext, DriverRegistry, systemHostProbe, } from '@kraken-e2e/core';
|
|
9
|
+
import { builtInChecks, driverGateChecks, runDoctor, } from '@kraken-e2e/doctor';
|
|
10
|
+
function toGateStatus(status) {
|
|
11
|
+
switch (status.state) {
|
|
12
|
+
case 'ready':
|
|
13
|
+
return { driverId: status.driver.manifest.id, state: 'ready' };
|
|
14
|
+
case 'unavailable-on-host':
|
|
15
|
+
return {
|
|
16
|
+
driverId: status.manifest.id,
|
|
17
|
+
state: 'unavailable-on-host',
|
|
18
|
+
detail: status.reason,
|
|
19
|
+
fix: status.fix,
|
|
20
|
+
};
|
|
21
|
+
case 'incompatible':
|
|
22
|
+
return {
|
|
23
|
+
driverId: status.id,
|
|
24
|
+
state: 'incompatible',
|
|
25
|
+
detail: `built against contract ${status.found.major}.${status.found.minor}, host supports ${status.supported.major}.${status.supported.minor}`,
|
|
26
|
+
fix: status.fix,
|
|
27
|
+
};
|
|
28
|
+
case 'invalid':
|
|
29
|
+
return { driverId: status.ref, state: 'invalid', detail: status.problems.join('; ') };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function buildDoctorReport(options) {
|
|
33
|
+
const host = systemHostProbe.detect();
|
|
34
|
+
const checks = [...builtInChecks()];
|
|
35
|
+
// Config is optional for doctor: without a project we still report the host.
|
|
36
|
+
try {
|
|
37
|
+
const config = await loadConfig({ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}) });
|
|
38
|
+
const registry = await DriverRegistry.create({
|
|
39
|
+
registrations: config.drivers,
|
|
40
|
+
host,
|
|
41
|
+
projectRoot: config.projectRoot,
|
|
42
|
+
});
|
|
43
|
+
checks.push(...driverGateChecks(registry.statuses().map(toGateStatus)));
|
|
44
|
+
for (const driver of registry.readyDrivers()) {
|
|
45
|
+
checks.push(...(driver.doctor ?? []));
|
|
46
|
+
}
|
|
47
|
+
return runDoctor({ host: createHostContext(host, config.projectRoot), checks });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// No project found — run the host-level checks only.
|
|
51
|
+
return runDoctor({ host: createHostContext(host), checks });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=doctor-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-report.js","sourceRoot":"","sources":["../src/doctor-report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,iBAAiB,EAEjB,cAAc,EAEd,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EAGb,gBAAgB,EAChB,SAAS,GACV,MAAM,oBAAoB,CAAC;AAE5B,SAAS,YAAY,CAAC,MAAoB;IACxC,QAAQ,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,KAAK,OAAO;YACV,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACjE,KAAK,qBAAqB;YACxB,OAAO;gBACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;gBAC5B,KAAK,EAAE,qBAAqB;gBAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;aAChB,CAAC;QACJ,KAAK,cAAc;YACjB,OAAO;gBACL,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,KAAK,EAAE,cAAc;gBACrB,MAAM,EAAE,0BAA0B,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,mBAAmB,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE;gBAC/I,GAAG,EAAE,MAAM,CAAC,GAAG;aAChB,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1F,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAyB;IAC/D,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,GAAG,aAAa,EAAE,CAAC,CAAC;IAEpC,6EAA6E;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAChG,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC;YAC3C,aAAa,EAAE,MAAM,CAAC,OAAwC;YAC9D,IAAI;YACJ,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxE,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;QACrD,OAAO,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kraken-e2e/cli — the `kraken` command (ADR-0001 §5.11). oclif v4, ESM,
|
|
3
|
+
* topicSeparator ' ' (colons remain aliases). Phase 1 ships `run` and
|
|
4
|
+
* `doctor` with the plain LineReporter; the Ink live UI and the
|
|
5
|
+
* Kraken-owned `plugins` topic arrive in Phase 2 (ADR-0001 §5.10/D15).
|
|
6
|
+
*/
|
|
7
|
+
export { buildDoctorReport } from './doctor-report.js';
|
|
8
|
+
export { type RunProjectOptions, type RunProjectResult, runProject } from './run-project.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kraken-e2e/cli — the `kraken` command (ADR-0001 §5.11). oclif v4, ESM,
|
|
3
|
+
* topicSeparator ' ' (colons remain aliases). Phase 1 ships `run` and
|
|
4
|
+
* `doctor` with the plain LineReporter; the Ink live UI and the
|
|
5
|
+
* Kraken-owned `plugins` topic arrive in Phase 2 (ADR-0001 §5.10/D15).
|
|
6
|
+
*/
|
|
7
|
+
export { buildDoctorReport } from './doctor-report.js';
|
|
8
|
+
export { runProject } from './run-project.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAiD,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-project.d.ts","sourceRoot":"","sources":["../src/init-project.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,sBAAsB;;;GAGlC,CAAC;AAkDF,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,GAAG,MAAM,CAiBT"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kraken init` scaffolding (ADR-0005): a runnable project skeleton plus the
|
|
3
|
+
* .vscode Cucumber settings that make {actor}/{duration} autocomplete work
|
|
4
|
+
* (the extension cannot see parameter types inside node_modules —
|
|
5
|
+
* ADR-0004 Appendix B). Never overwrites existing files.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
// Kept byte-identical to @kraken-e2e/gherkin's ACTOR_REGEXP/DURATION_REGEXP
|
|
10
|
+
// sources (ADR-0004 Appendix B) — the runtime matcher and the editor must
|
|
11
|
+
// agree. There is a test pinning this.
|
|
12
|
+
export const VSCODE_PARAMETER_TYPES = [
|
|
13
|
+
{ name: 'actor', regexp: '[a-zA-Z][a-zA-Z0-9_-]*|"[^"]+"' },
|
|
14
|
+
{ name: 'duration', regexp: '\\d+(?:\\.\\d+)?(?:ms|s|m)' },
|
|
15
|
+
];
|
|
16
|
+
const FILES = {
|
|
17
|
+
'kraken.config.ts': `import { defineConfig } from '@kraken-e2e/config';
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
actors: {
|
|
21
|
+
// The CLOSED actor set: steps naming anyone else are compile errors.
|
|
22
|
+
alice: { platform: 'android' },
|
|
23
|
+
bob: { platform: 'ios' },
|
|
24
|
+
},
|
|
25
|
+
drivers: [
|
|
26
|
+
// Install drivers with: kraken plugins install @kraken-e2e/driver-android
|
|
27
|
+
],
|
|
28
|
+
features: 'features/**/*.feature',
|
|
29
|
+
steps: './steps/index.ts',
|
|
30
|
+
});
|
|
31
|
+
`,
|
|
32
|
+
'steps/index.ts': `import { createStepRegistry } from '@kraken-e2e/gherkin';
|
|
33
|
+
|
|
34
|
+
// Destructure — bare Given/When/Then call sites are what the Cucumber VS Code
|
|
35
|
+
// extension indexes (namespaced calls lose autocomplete).
|
|
36
|
+
export const { Given, When, Then, defineParameterType, registry } = createStepRegistry();
|
|
37
|
+
|
|
38
|
+
// Your app-domain steps live here. Example:
|
|
39
|
+
// When('{actor} sends the message {string}', async ({ actor }, ...args) => {
|
|
40
|
+
// const [message] = args as unknown as [string];
|
|
41
|
+
// await actor.session.typeText({ by: 'testId', value: 'composer' }, message);
|
|
42
|
+
// await actor.session.tap({ by: 'testId', value: 'send' });
|
|
43
|
+
// });
|
|
44
|
+
`,
|
|
45
|
+
'features/example.feature': `Feature: My first choreography
|
|
46
|
+
Steps run in text order (the screenplay); signals and background tasks are
|
|
47
|
+
the escape hatches. See the Kraken docs for the built-in vocabulary.
|
|
48
|
+
|
|
49
|
+
Scenario: two users, one story
|
|
50
|
+
# When alice sends the message "hola"
|
|
51
|
+
# Then bob waits for the signal "message-sent" within 10s
|
|
52
|
+
`,
|
|
53
|
+
'.vscode/settings.json': `${JSON.stringify({
|
|
54
|
+
'cucumber.features': ['features/**/*.feature'],
|
|
55
|
+
'cucumber.glue': ['steps/**/*.ts'],
|
|
56
|
+
'cucumber.parameterTypes': VSCODE_PARAMETER_TYPES,
|
|
57
|
+
}, null, 2)}\n`,
|
|
58
|
+
};
|
|
59
|
+
export function initProject(options) {
|
|
60
|
+
for (const [relative, content] of Object.entries(FILES)) {
|
|
61
|
+
const path = join(options.cwd, relative);
|
|
62
|
+
if (existsSync(path)) {
|
|
63
|
+
options.write(`skip (exists): ${relative}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
67
|
+
writeFileSync(path, content);
|
|
68
|
+
options.write(`created: ${relative}`);
|
|
69
|
+
}
|
|
70
|
+
options.write('\nNext steps:');
|
|
71
|
+
options.write(' 1. kraken plugins install @kraken-e2e/driver-android (and/or -ios, -web)');
|
|
72
|
+
options.write(' 2. write steps in steps/index.ts and scenarios in features/');
|
|
73
|
+
options.write(' 3. kraken doctor (verify your environment)');
|
|
74
|
+
options.write(' 4. kraken run');
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=init-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-project.js","sourceRoot":"","sources":["../src/init-project.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,4EAA4E;AAC5E,0EAA0E;AAC1E,uCAAuC;AACvC,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,gCAAgC,EAAE;IAC3D,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,EAAE;CAC3D,CAAC;AAEF,MAAM,KAAK,GAA2B;IACpC,kBAAkB,EAAE;;;;;;;;;;;;;;CAcrB;IACC,gBAAgB,EAAE;;;;;;;;;;;;CAYnB;IACC,0BAA0B,EAAE;;;;;;;CAO7B;IACC,uBAAuB,EAAE,GAAG,IAAI,CAAC,SAAS,CACxC;QACE,mBAAmB,EAAE,CAAC,uBAAuB,CAAC;QAC9C,eAAe,EAAE,CAAC,eAAe,CAAC;QAClC,yBAAyB,EAAE,sBAAsB;KAClD,EACD,IAAI,EACJ,CAAC,CACF,IAAI;CACN,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,OAG3B;IACC,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,kBAAkB,QAAQ,EAAE,CAAC,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAC9F,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;IAC/E,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type HostInfo } from '@kraken-e2e/contracts';
|
|
2
|
+
export type PackageManager = 'pnpm' | 'npm' | 'yarn';
|
|
3
|
+
export declare function detectPackageManager(projectRoot: string): PackageManager;
|
|
4
|
+
export interface InstallPluginOptions {
|
|
5
|
+
readonly packageName: string;
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
/** Validate+register only (tests; also useful after a manual install). */
|
|
8
|
+
readonly skipInstall?: boolean;
|
|
9
|
+
readonly host: HostInfo;
|
|
10
|
+
readonly write: (line: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface InstallPluginResult {
|
|
13
|
+
readonly exitCode: number;
|
|
14
|
+
readonly registeredInConfig: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function installPlugin(options: InstallPluginOptions): Promise<InstallPluginResult>;
|
|
17
|
+
//# sourceMappingURL=plugins-install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins-install.d.ts","sourceRoot":"","sources":["../src/plugins-install.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,QAAQ,EAId,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAErD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAIxE;AAQD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAID,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA4I/F"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kraken plugins install` (ADR-0001 §5.10/D15, ADR-0005): Kraken OWNS the
|
|
3
|
+
* plugins topic. Drivers are exact-pinned project devDependencies — installed
|
|
4
|
+
* by the project's own package manager, validated against the contract, and
|
|
5
|
+
* registered in kraken.config.ts. @oclif/plugin-plugins is deliberately not
|
|
6
|
+
* used (per-user dataDir state outside the lockfile).
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { findConfigPath } from '@kraken-e2e/config';
|
|
14
|
+
import { CONTRACT_VERSION, checkHostRequirements, isContractCompatible, isKrakenDriver, KrakenError, } from '@kraken-e2e/contracts';
|
|
15
|
+
export function detectPackageManager(projectRoot) {
|
|
16
|
+
if (existsSync(join(projectRoot, 'pnpm-lock.yaml')))
|
|
17
|
+
return 'pnpm';
|
|
18
|
+
if (existsSync(join(projectRoot, 'yarn.lock')))
|
|
19
|
+
return 'yarn';
|
|
20
|
+
return 'npm';
|
|
21
|
+
}
|
|
22
|
+
const INSTALL_ARGS = {
|
|
23
|
+
pnpm: ['add', '-D', '-E'],
|
|
24
|
+
npm: ['install', '--save-dev', '--save-exact'],
|
|
25
|
+
yarn: ['add', '--dev', '--exact'],
|
|
26
|
+
};
|
|
27
|
+
const NPM_NAME_GRAMMAR = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
28
|
+
export async function installPlugin(options) {
|
|
29
|
+
const { packageName, write } = options;
|
|
30
|
+
const cwd = options.cwd ?? process.cwd();
|
|
31
|
+
// Never forward untrusted strings to a package manager: a name starting
|
|
32
|
+
// with '-' would be parsed as a FLAG (registry override, script injection).
|
|
33
|
+
if (!NPM_NAME_GRAMMAR.test(packageName)) {
|
|
34
|
+
write(`✗ "${packageName}" is not a valid npm package name.`);
|
|
35
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
36
|
+
}
|
|
37
|
+
// 1 — Locate the project (never a silent global install — ADR-0001 §5.10).
|
|
38
|
+
const configPath = findConfigPath(cwd);
|
|
39
|
+
const projectRoot = configPath
|
|
40
|
+
? join(configPath, '..')
|
|
41
|
+
: existsSync(join(cwd, 'package.json'))
|
|
42
|
+
? cwd
|
|
43
|
+
: undefined;
|
|
44
|
+
if (!projectRoot) {
|
|
45
|
+
write('✗ No Kraken project found (no kraken.config.* upwards, no package.json here).');
|
|
46
|
+
write(' fix: run inside your test project, or create one with: kraken init');
|
|
47
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
48
|
+
}
|
|
49
|
+
// 2 — Install through the project's OWN package manager (lockfile-governed).
|
|
50
|
+
if (options.skipInstall !== true) {
|
|
51
|
+
const pm = detectPackageManager(projectRoot);
|
|
52
|
+
write(`Installing ${packageName} with ${pm} (exact-pinned devDependency)…`);
|
|
53
|
+
const result = spawnSync(pm, [...INSTALL_ARGS[pm], packageName], {
|
|
54
|
+
cwd: projectRoot,
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
// Windows package managers are .cmd shims — they need a shell there.
|
|
57
|
+
shell: process.platform === 'win32',
|
|
58
|
+
});
|
|
59
|
+
if (result.error) {
|
|
60
|
+
write(`✗ could not launch ${pm}: ${result.error.message}`);
|
|
61
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
62
|
+
}
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
write(`✗ ${pm} exited with ${result.status ?? 'a signal'} — nothing was registered.`);
|
|
65
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 3 — Pre-gate via /manifest FIRST (ADR-0001 §5.5, same rule as the
|
|
69
|
+
// registry): on an unsupported host the MAIN ENTRY is never imported —
|
|
70
|
+
// it may legitimately fail to load there.
|
|
71
|
+
const resolve = createRequire(join(projectRoot, 'package.json')).resolve;
|
|
72
|
+
let manifest;
|
|
73
|
+
try {
|
|
74
|
+
const manifestModule = (await import(pathToFileURL(resolve(`${packageName}/manifest`)).href));
|
|
75
|
+
const candidate = manifestModule.default ?? manifestModule.manifest;
|
|
76
|
+
if (!candidate)
|
|
77
|
+
throw new Error('no default (or named `manifest`) export');
|
|
78
|
+
manifest = candidate;
|
|
79
|
+
}
|
|
80
|
+
catch (cause) {
|
|
81
|
+
write(`✗ ${packageName}/manifest could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
82
|
+
write(' Kraken drivers must ship a /manifest subpath (ADR-0001 §5.5).');
|
|
83
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
84
|
+
}
|
|
85
|
+
if (!isContractCompatible(manifest.contract, CONTRACT_VERSION)) {
|
|
86
|
+
write(`✗ ${packageName} was built against contract ` +
|
|
87
|
+
`${manifest.contract.major}.${manifest.contract.minor}; this Kraken supports ` +
|
|
88
|
+
`${CONTRACT_VERSION.major}.${CONTRACT_VERSION.minor}. Align the versions.`);
|
|
89
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
90
|
+
}
|
|
91
|
+
// 4 — ADVISORY host gate (install is cross-platform; load/run is what gates).
|
|
92
|
+
const gate = checkHostRequirements(manifest.hostRequirements, options.host);
|
|
93
|
+
if (!gate.ok) {
|
|
94
|
+
write(`! ${manifest.platformLabel} is DISABLED on this host: ${gate.reason}.`);
|
|
95
|
+
write(' Installed and lockfile-pinned anyway (your teammates on supported hosts get it).');
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Host is fine — the full brand validation can import the entry safely.
|
|
99
|
+
try {
|
|
100
|
+
const entry = (await import(pathToFileURL(resolve(packageName)).href));
|
|
101
|
+
if (typeof entry.default !== 'function' || !isKrakenDriver(entry.default())) {
|
|
102
|
+
throw new KrakenError('KRK-PLUGIN-INVALID', `${packageName}'s default export is not a defineDriver() factory.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (cause) {
|
|
106
|
+
write(`✗ ${packageName} could not be validated: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
107
|
+
write(' The package stays installed; fix it or remove it with your package manager.');
|
|
108
|
+
return { exitCode: 1, registeredInConfig: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// 5 — Config registration: append the string form when mechanically safe,
|
|
112
|
+
// otherwise print the exact lines (never rewrite user code blindly).
|
|
113
|
+
let registered = false;
|
|
114
|
+
if (configPath) {
|
|
115
|
+
const source = readFileSync(configPath, 'utf8');
|
|
116
|
+
if (source.includes(`'${packageName}'`) || source.includes(`"${packageName}"`)) {
|
|
117
|
+
write(`✓ ${packageName} is already registered in ${configPath}.`);
|
|
118
|
+
registered = true;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Comment-aware: only append at a drivers:[ marker on a NON-comment line
|
|
122
|
+
// (a commented-out example above the real array must not swallow it).
|
|
123
|
+
const lines = source.split('\n');
|
|
124
|
+
const markerLine = lines.findIndex((line) => {
|
|
125
|
+
const match = /\bdrivers\s*:\s*\[/.exec(line);
|
|
126
|
+
return match !== null && !line.slice(0, match.index).includes('//');
|
|
127
|
+
});
|
|
128
|
+
if (markerLine !== -1) {
|
|
129
|
+
lines[markerLine] = lines[markerLine].replace(/(\bdrivers\s*:\s*\[)/, `$1\n '${packageName}',`);
|
|
130
|
+
writeFileSync(configPath, lines.join('\n'));
|
|
131
|
+
write(`✓ registered '${packageName}' in ${configPath}.`);
|
|
132
|
+
registered = true;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
write(`Add it to your config's drivers array:\n drivers: ['${packageName}', …],`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
write(`No kraken.config.ts found — create one (kraken init) and register:\n drivers: ['${packageName}'],`);
|
|
141
|
+
}
|
|
142
|
+
// 6 — Epilogue.
|
|
143
|
+
for (const hint of manifest.setupHints ?? []) {
|
|
144
|
+
write(` next: ${hint}`);
|
|
145
|
+
}
|
|
146
|
+
return { exitCode: 0, registeredInConfig: registered };
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=plugins-install.js.map
|