@jskit-ai/jskit-cli 0.2.54 → 0.2.56
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/package.json +4 -4
- package/src/server/cliRuntime/appState.js +1 -0
- package/src/server/commandHandlers/app.js +4 -0
- package/src/server/commandHandlers/appCommandCatalog.js +29 -0
- package/src/server/commandHandlers/appCommands/shared.js +31 -0
- package/src/server/commandHandlers/appCommands/verifyUi.js +102 -0
- package/src/server/commandHandlers/health.js +54 -0
- package/src/server/core/buildCommandDeps.js +1 -0
- package/src/server/core/createCliRunner.js +2 -0
- package/src/server/shared/uiVerification.js +148 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.55",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.47",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.46"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "20.x"
|
|
@@ -13,6 +13,7 @@ import { runAppLinkLocalPackagesCommand } from "./appCommands/linkLocalPackages.
|
|
|
13
13
|
import { runAppReleaseCommand } from "./appCommands/release.js";
|
|
14
14
|
import { runAppUpdatePackagesCommand } from "./appCommands/updatePackages.js";
|
|
15
15
|
import { runAppVerifyCommand } from "./appCommands/verify.js";
|
|
16
|
+
import { runAppVerifyUiCommand } from "./appCommands/verifyUi.js";
|
|
16
17
|
|
|
17
18
|
function renderAppHelp(stream, definition = null) {
|
|
18
19
|
const color = createColorFormatter(stream);
|
|
@@ -135,6 +136,9 @@ function createAppCommands(ctx = {}) {
|
|
|
135
136
|
if (definition.name === "verify") {
|
|
136
137
|
return runAppVerifyCommand(ctx, { appRoot, options, stdout, stderr });
|
|
137
138
|
}
|
|
139
|
+
if (definition.name === "verify-ui") {
|
|
140
|
+
return runAppVerifyUiCommand(ctx, { appRoot, options, stdout, stderr });
|
|
141
|
+
}
|
|
138
142
|
if (definition.name === "update-packages") {
|
|
139
143
|
return runAppUpdatePackagesCommand(ctx, { appRoot, options, stdout, stderr });
|
|
140
144
|
}
|
|
@@ -38,6 +38,30 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
38
38
|
"The scaffolded npm run verify wrapper can append npm run --if-present verify:app afterwards."
|
|
39
39
|
])
|
|
40
40
|
}),
|
|
41
|
+
"verify-ui": Object.freeze({
|
|
42
|
+
name: "verify-ui",
|
|
43
|
+
summary: "Run a targeted Playwright command and write a UI verification receipt for jskit doctor.",
|
|
44
|
+
usage: "jskit app verify-ui --command <shell-command> --feature <label> --auth-mode <mode>",
|
|
45
|
+
options: Object.freeze([
|
|
46
|
+
Object.freeze({
|
|
47
|
+
label: "--command <shell-command>",
|
|
48
|
+
description: "Targeted Playwright command to run, for example: npx playwright test tests/e2e/contacts.spec.ts -g filters."
|
|
49
|
+
}),
|
|
50
|
+
Object.freeze({
|
|
51
|
+
label: "--feature <label>",
|
|
52
|
+
description: "Short human label for the UI feature or flow that was verified."
|
|
53
|
+
}),
|
|
54
|
+
Object.freeze({
|
|
55
|
+
label: "--auth-mode <mode>",
|
|
56
|
+
description: "Auth path used by the Playwright flow: none | dev-auth-login-as | session-bootstrap | custom-local."
|
|
57
|
+
})
|
|
58
|
+
]),
|
|
59
|
+
defaults: Object.freeze([
|
|
60
|
+
"Requires a git working tree so the receipt can record the currently changed UI files.",
|
|
61
|
+
"Writes .jskit/verification/ui.json after the command succeeds.",
|
|
62
|
+
"Doctor expects the receipt to match the current dirty UI file set."
|
|
63
|
+
])
|
|
64
|
+
}),
|
|
41
65
|
"update-packages": Object.freeze({
|
|
42
66
|
name: "update-packages",
|
|
43
67
|
summary: "Update installed @jskit-ai dependencies and refresh managed migrations.",
|
|
@@ -152,6 +176,11 @@ function buildAppCommandOptionMeta(subcommandName = "") {
|
|
|
152
176
|
if (definition.name === "update-packages" || definition.name === "release") {
|
|
153
177
|
optionMeta.registry = { inputType: "text" };
|
|
154
178
|
}
|
|
179
|
+
if (definition.name === "verify-ui") {
|
|
180
|
+
optionMeta.command = { inputType: "text" };
|
|
181
|
+
optionMeta.feature = { inputType: "text" };
|
|
182
|
+
optionMeta["auth-mode"] = { inputType: "text" };
|
|
183
|
+
}
|
|
155
184
|
if (definition.name === "link-local-packages") {
|
|
156
185
|
optionMeta["repo-root"] = { inputType: "text" };
|
|
157
186
|
}
|
|
@@ -80,6 +80,36 @@ function runExternalCommand(
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function runExternalShellCommand(
|
|
84
|
+
commandText,
|
|
85
|
+
{
|
|
86
|
+
cwd = "",
|
|
87
|
+
env = {},
|
|
88
|
+
stdout,
|
|
89
|
+
stderr,
|
|
90
|
+
quiet = false,
|
|
91
|
+
createCliError
|
|
92
|
+
} = {}
|
|
93
|
+
) {
|
|
94
|
+
const result = spawnSync(String(commandText || ""), {
|
|
95
|
+
cwd: cwd || process.cwd(),
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
shell: true,
|
|
98
|
+
env: {
|
|
99
|
+
...process.env,
|
|
100
|
+
...env
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return ensureCommandSucceeded(result, String(commandText || "").trim() || "shell command", {
|
|
105
|
+
createCliError,
|
|
106
|
+
cwd,
|
|
107
|
+
stdout,
|
|
108
|
+
stderr,
|
|
109
|
+
quiet
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
function formatUtcReleaseTimestamp(date = new Date()) {
|
|
84
114
|
const year = date.getUTCFullYear();
|
|
85
115
|
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
@@ -265,6 +295,7 @@ export {
|
|
|
265
295
|
normalizeText,
|
|
266
296
|
isTruthyFlag,
|
|
267
297
|
runExternalCommand,
|
|
298
|
+
runExternalShellCommand,
|
|
268
299
|
formatUtcReleaseTimestamp,
|
|
269
300
|
resolveLocalJskitBin,
|
|
270
301
|
runLocalJskit,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runExternalShellCommand } from "./shared.js";
|
|
4
|
+
import {
|
|
5
|
+
directoryLooksLikeJskitAppRoot
|
|
6
|
+
} from "../../cliRuntime/appState.js";
|
|
7
|
+
import {
|
|
8
|
+
UI_VERIFICATION_RECEIPT_RELATIVE_PATH,
|
|
9
|
+
UI_VERIFICATION_RECEIPT_VERSION,
|
|
10
|
+
UI_VERIFICATION_RUNNER,
|
|
11
|
+
isUiVerificationAuthMode,
|
|
12
|
+
resolveChangedUiFilesFromGit
|
|
13
|
+
} from "../../shared/uiVerification.js";
|
|
14
|
+
|
|
15
|
+
async function runAppVerifyUiCommand(ctx = {}, { appRoot = "", options = {}, stdout, stderr }) {
|
|
16
|
+
const { createCliError } = ctx;
|
|
17
|
+
|
|
18
|
+
if (options?.dryRun) {
|
|
19
|
+
throw createCliError("jskit app verify-ui does not support --dry-run.", { exitCode: 1 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const inlineOptions =
|
|
23
|
+
options?.inlineOptions && typeof options.inlineOptions === "object" ? options.inlineOptions : {};
|
|
24
|
+
const command = String(inlineOptions.command || "").trim();
|
|
25
|
+
const feature = String(inlineOptions.feature || "").trim();
|
|
26
|
+
const authMode = String(inlineOptions["auth-mode"] || "").trim();
|
|
27
|
+
|
|
28
|
+
if (!command) {
|
|
29
|
+
throw createCliError("jskit app verify-ui requires --command <shell-command>.", {
|
|
30
|
+
exitCode: 1
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!feature) {
|
|
34
|
+
throw createCliError("jskit app verify-ui requires --feature <label>.", {
|
|
35
|
+
exitCode: 1
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!isUiVerificationAuthMode(authMode)) {
|
|
39
|
+
throw createCliError(
|
|
40
|
+
"jskit app verify-ui requires --auth-mode <none|dev-auth-login-as|session-bootstrap|custom-local>.",
|
|
41
|
+
{
|
|
42
|
+
exitCode: 1
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!(await directoryLooksLikeJskitAppRoot(appRoot))) {
|
|
47
|
+
throw createCliError(
|
|
48
|
+
"jskit app verify-ui only works in a JSKIT app root (expected app.json or .jskit/lock.json).",
|
|
49
|
+
{
|
|
50
|
+
exitCode: 1
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const changedUiState = resolveChangedUiFilesFromGit(appRoot);
|
|
56
|
+
if (!changedUiState.available) {
|
|
57
|
+
throw createCliError("jskit app verify-ui requires a git working tree so it can record changed UI files.", {
|
|
58
|
+
exitCode: 1
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (changedUiState.paths.length < 1) {
|
|
62
|
+
throw createCliError("jskit app verify-ui found no changed UI files in src/ or packages/.", {
|
|
63
|
+
exitCode: 1
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
runExternalShellCommand(command, {
|
|
68
|
+
cwd: appRoot,
|
|
69
|
+
stdout,
|
|
70
|
+
stderr,
|
|
71
|
+
createCliError
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const recordedAt = new Date().toISOString();
|
|
75
|
+
const receiptPath = path.join(appRoot, UI_VERIFICATION_RECEIPT_RELATIVE_PATH);
|
|
76
|
+
await mkdir(path.dirname(receiptPath), { recursive: true });
|
|
77
|
+
await writeFile(
|
|
78
|
+
receiptPath,
|
|
79
|
+
`${JSON.stringify(
|
|
80
|
+
{
|
|
81
|
+
version: UI_VERIFICATION_RECEIPT_VERSION,
|
|
82
|
+
runner: UI_VERIFICATION_RUNNER,
|
|
83
|
+
recordedAt,
|
|
84
|
+
feature,
|
|
85
|
+
command,
|
|
86
|
+
authMode,
|
|
87
|
+
changedUiFiles: changedUiState.paths
|
|
88
|
+
},
|
|
89
|
+
null,
|
|
90
|
+
2
|
|
91
|
+
)}\n`,
|
|
92
|
+
"utf8"
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
stdout.write(
|
|
96
|
+
`[verify-ui] wrote ${UI_VERIFICATION_RECEIPT_RELATIVE_PATH} for ${changedUiState.paths.length} changed UI file(s).\n`
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { runAppVerifyUiCommand };
|
|
@@ -7,9 +7,16 @@ import {
|
|
|
7
7
|
ensureObject,
|
|
8
8
|
sortStrings
|
|
9
9
|
} from "../shared/collectionUtils.js";
|
|
10
|
+
import {
|
|
11
|
+
UI_VERIFICATION_RECEIPT_RELATIVE_PATH,
|
|
12
|
+
isValidUiVerificationReceipt,
|
|
13
|
+
normalizeUiVerificationReceipt,
|
|
14
|
+
resolveChangedUiFilesFromGit
|
|
15
|
+
} from "../shared/uiVerification.js";
|
|
10
16
|
|
|
11
17
|
function createHealthCommands(ctx = {}) {
|
|
12
18
|
const {
|
|
19
|
+
directoryLooksLikeJskitAppRoot,
|
|
13
20
|
resolveAppRootFromCwd,
|
|
14
21
|
loadLockFile,
|
|
15
22
|
loadPackageRegistry,
|
|
@@ -621,6 +628,49 @@ function createHealthCommands(ctx = {}) {
|
|
|
621
628
|
}
|
|
622
629
|
}
|
|
623
630
|
|
|
631
|
+
async function collectUiVerificationDoctorIssues({ appRoot, issues }) {
|
|
632
|
+
if (!(await directoryLooksLikeJskitAppRoot(appRoot))) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const changedUiState = resolveChangedUiFilesFromGit(appRoot);
|
|
637
|
+
if (!changedUiState.available || changedUiState.paths.length < 1) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const receiptPath = path.join(appRoot, UI_VERIFICATION_RECEIPT_RELATIVE_PATH);
|
|
642
|
+
if (!(await fileExists(receiptPath))) {
|
|
643
|
+
issues.push(
|
|
644
|
+
`[ui:verification] changed UI files require a matching ${UI_VERIFICATION_RECEIPT_RELATIVE_PATH} receipt. Run jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>. Current files: ${changedUiState.paths.join(", ")}`
|
|
645
|
+
);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
let parsedReceipt = null;
|
|
650
|
+
try {
|
|
651
|
+
parsedReceipt = JSON.parse(await readFile(receiptPath, "utf8"));
|
|
652
|
+
} catch (error) {
|
|
653
|
+
issues.push(
|
|
654
|
+
`[ui:verification] ${UI_VERIFICATION_RECEIPT_RELATIVE_PATH} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
655
|
+
);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const receipt = normalizeUiVerificationReceipt(parsedReceipt);
|
|
660
|
+
if (!isValidUiVerificationReceipt(receipt)) {
|
|
661
|
+
issues.push(
|
|
662
|
+
`[ui:verification] ${UI_VERIFICATION_RECEIPT_RELATIVE_PATH} is incomplete. It must include version, runner, recordedAt, feature, command, authMode, and changedUiFiles from jskit app verify-ui.`
|
|
663
|
+
);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (JSON.stringify(receipt.changedUiFiles) !== JSON.stringify(changedUiState.paths)) {
|
|
668
|
+
issues.push(
|
|
669
|
+
`[ui:verification] ${UI_VERIFICATION_RECEIPT_RELATIVE_PATH} does not match the current changed UI file set. Re-run jskit app verify-ui after the latest UI edits. Current files: ${changedUiState.paths.join(", ")}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
624
674
|
function collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }) {
|
|
625
675
|
const packageId = String(packageEntry?.packageId || "").trim();
|
|
626
676
|
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
@@ -713,6 +763,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
713
763
|
appRoot,
|
|
714
764
|
issues
|
|
715
765
|
});
|
|
766
|
+
await collectUiVerificationDoctorIssues({
|
|
767
|
+
appRoot,
|
|
768
|
+
issues
|
|
769
|
+
});
|
|
716
770
|
|
|
717
771
|
const payload = {
|
|
718
772
|
appRoot,
|
|
@@ -6,6 +6,7 @@ function createCommandHandlerDeps(deps = {}) {
|
|
|
6
6
|
writeWrappedItems: deps.writeWrappedItems,
|
|
7
7
|
normalizeRelativePath: deps.normalizeRelativePath,
|
|
8
8
|
normalizeRelativePosixPath: deps.normalizeRelativePosixPath,
|
|
9
|
+
directoryLooksLikeJskitAppRoot: deps.directoryLooksLikeJskitAppRoot,
|
|
9
10
|
resolveAppRootFromCwd: deps.resolveAppRootFromCwd,
|
|
10
11
|
loadLockFile: deps.loadLockFile,
|
|
11
12
|
loadPackageRegistry: deps.loadPackageRegistry,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
readFileBufferIfExists
|
|
34
34
|
} from "../cliRuntime/ioAndMigrations.js";
|
|
35
35
|
import {
|
|
36
|
+
directoryLooksLikeJskitAppRoot,
|
|
36
37
|
resolveAppRootFromCwd,
|
|
37
38
|
loadAppPackageJson,
|
|
38
39
|
loadLockFile,
|
|
@@ -97,6 +98,7 @@ const commandHandlers = createCommandHandlers(
|
|
|
97
98
|
writeWrappedItems,
|
|
98
99
|
normalizeRelativePath,
|
|
99
100
|
normalizeRelativePosixPath,
|
|
101
|
+
directoryLooksLikeJskitAppRoot,
|
|
100
102
|
resolveAppRootFromCwd,
|
|
101
103
|
loadLockFile,
|
|
102
104
|
loadPackageRegistry,
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const UI_VERIFICATION_RECEIPT_VERSION = 1;
|
|
5
|
+
const UI_VERIFICATION_RECEIPT_RELATIVE_PATH = ".jskit/verification/ui.json";
|
|
6
|
+
const UI_VERIFICATION_RUNNER = "playwright";
|
|
7
|
+
const UI_VERIFICATION_AUTH_MODES = new Set([
|
|
8
|
+
"none",
|
|
9
|
+
"dev-auth-login-as",
|
|
10
|
+
"session-bootstrap",
|
|
11
|
+
"custom-local"
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const UI_EXTENSION_SET = new Set([
|
|
15
|
+
".vue"
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const UI_SCRIPT_PATH_PATTERNS = Object.freeze([
|
|
19
|
+
/^src\/components\//u,
|
|
20
|
+
/^src\/composables\//u,
|
|
21
|
+
/^src\/layouts\//u,
|
|
22
|
+
/^src\/pages\//u,
|
|
23
|
+
/^src\/stores\//u,
|
|
24
|
+
/^src\/placement\.[A-Za-z0-9]+$/u,
|
|
25
|
+
/^packages\/[^/]+(?:-web)?\/src\/client\//u
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function normalizeText(value = "") {
|
|
29
|
+
return String(value || "").trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sortUniqueStrings(values = []) {
|
|
33
|
+
return [...new Set(values.map((value) => normalizeText(value)).filter(Boolean))].sort((left, right) =>
|
|
34
|
+
left.localeCompare(right)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isUiVerificationAuthMode(value = "") {
|
|
39
|
+
return UI_VERIFICATION_AUTH_MODES.has(normalizeText(value));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isUiVerificationPath(relativePath = "") {
|
|
43
|
+
const normalizedPath = normalizeText(relativePath).replace(/\\/g, "/");
|
|
44
|
+
if (!normalizedPath) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (UI_EXTENSION_SET.has(path.extname(normalizedPath).toLowerCase())) {
|
|
49
|
+
return normalizedPath.startsWith("src/") || normalizedPath.startsWith("packages/");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return UI_SCRIPT_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readGitPathList(appRoot = "", args = []) {
|
|
56
|
+
const result = spawnSync("git", Array.isArray(args) ? args : [], {
|
|
57
|
+
cwd: appRoot || process.cwd(),
|
|
58
|
+
encoding: "utf8"
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result?.error || result?.status !== 0) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
paths: []
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
paths: sortUniqueStrings(String(result.stdout || "").split(/\r?\n/u))
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveChangedUiFilesFromGit(appRoot = "") {
|
|
75
|
+
const gitRepoCheck = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
76
|
+
cwd: appRoot || process.cwd(),
|
|
77
|
+
encoding: "utf8"
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
gitRepoCheck?.error ||
|
|
82
|
+
gitRepoCheck?.status !== 0 ||
|
|
83
|
+
normalizeText(gitRepoCheck.stdout).toLowerCase() !== "true"
|
|
84
|
+
) {
|
|
85
|
+
return {
|
|
86
|
+
available: false,
|
|
87
|
+
paths: []
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const changedPathSets = [
|
|
92
|
+
readGitPathList(appRoot, ["diff", "--name-only", "--relative", "--cached", "--", "src", "packages"]),
|
|
93
|
+
readGitPathList(appRoot, ["diff", "--name-only", "--relative", "--", "src", "packages"]),
|
|
94
|
+
readGitPathList(appRoot, ["ls-files", "--others", "--exclude-standard", "--", "src", "packages"])
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const changedPaths = sortUniqueStrings(
|
|
98
|
+
changedPathSets
|
|
99
|
+
.filter((entry) => entry.ok)
|
|
100
|
+
.flatMap((entry) => entry.paths)
|
|
101
|
+
.filter((relativePath) => isUiVerificationPath(relativePath))
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
available: true,
|
|
106
|
+
paths: changedPaths
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeUiVerificationReceipt(rawReceipt = {}) {
|
|
111
|
+
const receipt = rawReceipt && typeof rawReceipt === "object" && !Array.isArray(rawReceipt) ? rawReceipt : {};
|
|
112
|
+
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
version: Number.isInteger(receipt.version) ? receipt.version : null,
|
|
115
|
+
runner: normalizeText(receipt.runner),
|
|
116
|
+
recordedAt: normalizeText(receipt.recordedAt),
|
|
117
|
+
feature: normalizeText(receipt.feature),
|
|
118
|
+
command: normalizeText(receipt.command),
|
|
119
|
+
authMode: normalizeText(receipt.authMode),
|
|
120
|
+
changedUiFiles: sortUniqueStrings(receipt.changedUiFiles)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isValidUiVerificationReceipt(receipt) {
|
|
125
|
+
const normalizedReceipt = normalizeUiVerificationReceipt(receipt);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
normalizedReceipt.version === UI_VERIFICATION_RECEIPT_VERSION &&
|
|
129
|
+
normalizedReceipt.runner === UI_VERIFICATION_RUNNER &&
|
|
130
|
+
Boolean(normalizedReceipt.recordedAt) &&
|
|
131
|
+
Boolean(normalizedReceipt.feature) &&
|
|
132
|
+
Boolean(normalizedReceipt.command) &&
|
|
133
|
+
isUiVerificationAuthMode(normalizedReceipt.authMode)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
UI_VERIFICATION_AUTH_MODES,
|
|
139
|
+
UI_VERIFICATION_RECEIPT_RELATIVE_PATH,
|
|
140
|
+
UI_VERIFICATION_RECEIPT_VERSION,
|
|
141
|
+
UI_VERIFICATION_RUNNER,
|
|
142
|
+
isUiVerificationAuthMode,
|
|
143
|
+
isUiVerificationPath,
|
|
144
|
+
isValidUiVerificationReceipt,
|
|
145
|
+
normalizeUiVerificationReceipt,
|
|
146
|
+
resolveChangedUiFilesFromGit,
|
|
147
|
+
sortUniqueStrings
|
|
148
|
+
};
|