@jskit-ai/jskit-cli 0.2.136 → 0.2.139
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 +6 -5
- package/src/server/cliRuntime/completion.js +1 -3
- package/src/server/commandHandlers/app.js +0 -4
- package/src/server/commandHandlers/appCommandCatalog.js +0 -24
- package/src/server/commandHandlers/appCommands/shared.js +4 -141
- package/src/server/commandHandlers/appCommands/updatePackages.js +64 -0
- package/src/server/commandHandlers/mobileCommandCatalog.js +1 -5
- package/src/server/commandHandlers/packageCommands/add.js +4 -26
- package/src/server/core/argParser.js +0 -6
- package/src/server/core/commandCatalog.js +6 -9
- package/src/test/playwright.js +95 -0
- package/src/server/commandHandlers/appCommands/linkLocalPackages.js +0 -197
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.139",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./src/index.js",
|
|
16
16
|
"./server": "./src/server/index.js",
|
|
17
|
-
"./client": "./src/client/index.js"
|
|
17
|
+
"./client": "./src/client/index.js",
|
|
18
|
+
"./test/playwright": "./src/test/playwright.js"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"test": "node --test"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
24
|
+
"@jskit-ai/jskit-catalog": "0.1.134",
|
|
25
|
+
"@jskit-ai/kernel": "0.1.122",
|
|
26
|
+
"@jskit-ai/shell-web": "0.1.123",
|
|
26
27
|
"@vue/compiler-sfc": "^3.5.29",
|
|
27
28
|
"ts-morph": "^28.0.0",
|
|
28
29
|
"yaml": "^2.8.3"
|
|
@@ -18,11 +18,10 @@ import {
|
|
|
18
18
|
} from "../core/commandCatalog.js";
|
|
19
19
|
|
|
20
20
|
const WRAPPER_COMMANDS = new Set(["npx", "jsx"]);
|
|
21
|
-
const KNOWN_GENERATE_FLAG_OPTIONS = Object.freeze(["dry-run", "run-npm-install", "
|
|
21
|
+
const KNOWN_GENERATE_FLAG_OPTIONS = Object.freeze(["dry-run", "run-npm-install", "json", "verbose"]);
|
|
22
22
|
const BOOLEAN_OPTION_NAMES = new Set([
|
|
23
23
|
"dry-run",
|
|
24
24
|
"run-npm-install",
|
|
25
|
-
"devlinks",
|
|
26
25
|
"full",
|
|
27
26
|
"expanded",
|
|
28
27
|
"details",
|
|
@@ -784,7 +783,6 @@ function buildCommandOptionMeta(command = "", catalogModule) {
|
|
|
784
783
|
const labels = {
|
|
785
784
|
dryRun: "dry-run",
|
|
786
785
|
runNpmInstall: "run-npm-install",
|
|
787
|
-
devlinks: "devlinks",
|
|
788
786
|
full: "full",
|
|
789
787
|
expanded: "expanded",
|
|
790
788
|
details: "details",
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
resolveAppCommandDefinition
|
|
10
10
|
} from "./appCommandCatalog.js";
|
|
11
11
|
import { runAppAdoptManagedScriptsCommand } from "./appCommands/adoptManagedScripts.js";
|
|
12
|
-
import { runAppLinkLocalPackagesCommand } from "./appCommands/linkLocalPackages.js";
|
|
13
12
|
import { runAppMigrateSourceMutationsCommand } from "./appCommands/migrateSourceMutations.js";
|
|
14
13
|
import { runAppReleaseCommand } from "./appCommands/release.js";
|
|
15
14
|
import { runAppSyncCiCommand } from "./appCommands/syncCi.js";
|
|
@@ -147,9 +146,6 @@ function createAppCommands(ctx = {}) {
|
|
|
147
146
|
if (definition.name === "sync-ci") {
|
|
148
147
|
return runAppSyncCiCommand(ctx, { appRoot, options, stdout, stderr });
|
|
149
148
|
}
|
|
150
|
-
if (definition.name === "link-local-packages") {
|
|
151
|
-
return runAppLinkLocalPackagesCommand(ctx, { appRoot, options, stdout, stderr });
|
|
152
|
-
}
|
|
153
149
|
if (definition.name === "release") {
|
|
154
150
|
return runAppReleaseCommand(ctx, { appRoot, options, stdout, stderr });
|
|
155
151
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const APP_SCRIPT_WRAPPERS = Object.freeze({
|
|
2
2
|
verify: "jskit app verify && npm run --if-present verify:app",
|
|
3
3
|
"jskit:update": "jskit app update-packages",
|
|
4
|
-
devlinks: "jskit app link-local-packages",
|
|
5
4
|
release: "jskit app release"
|
|
6
5
|
});
|
|
7
6
|
|
|
@@ -12,9 +11,6 @@ const COPIED_APP_SCRIPT_VALUES = Object.freeze({
|
|
|
12
11
|
"jskit:update": Object.freeze([
|
|
13
12
|
"bash ./scripts/update-jskit-packages.sh"
|
|
14
13
|
]),
|
|
15
|
-
devlinks: Object.freeze([
|
|
16
|
-
"bash ./scripts/link-local-jskit-packages.sh"
|
|
17
|
-
]),
|
|
18
14
|
release: Object.freeze([
|
|
19
15
|
"bash ./scripts/release.sh"
|
|
20
16
|
])
|
|
@@ -22,7 +18,6 @@ const COPIED_APP_SCRIPT_VALUES = Object.freeze({
|
|
|
22
18
|
|
|
23
19
|
const COPIED_APP_SCRIPT_FILES = Object.freeze([
|
|
24
20
|
"scripts/update-jskit-packages.sh",
|
|
25
|
-
"scripts/link-local-jskit-packages.sh",
|
|
26
21
|
"scripts/release.sh"
|
|
27
22
|
]);
|
|
28
23
|
|
|
@@ -110,22 +105,6 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
110
105
|
"Never claims an unrecorded jskit-verify.yml or removes a customized legacy verify.yml."
|
|
111
106
|
])
|
|
112
107
|
}),
|
|
113
|
-
"link-local-packages": Object.freeze({
|
|
114
|
-
name: "link-local-packages",
|
|
115
|
-
summary: "Link local @jskit-ai workspace packages into the current app for live development.",
|
|
116
|
-
usage: "jskit app link-local-packages [--repo-root <path>]",
|
|
117
|
-
options: Object.freeze([
|
|
118
|
-
Object.freeze({
|
|
119
|
-
label: "--repo-root <path>",
|
|
120
|
-
description: "Explicit jskit-ai monorepo checkout to link from. If omitted, JSKIT_REPO_ROOT or nearby jskit-ai directories are used."
|
|
121
|
-
})
|
|
122
|
-
]),
|
|
123
|
-
defaults: Object.freeze([
|
|
124
|
-
"Links packages from both the monorepo packages/ and tooling/ directories.",
|
|
125
|
-
"Refreshes node_modules/.bin entries for linked packages that publish binaries.",
|
|
126
|
-
"Clears node_modules/.vite so Vite does not keep stale prebundled paths."
|
|
127
|
-
])
|
|
128
|
-
}),
|
|
129
108
|
release: Object.freeze({
|
|
130
109
|
name: "release",
|
|
131
110
|
summary: "Run the JSKIT release helper for an app repository.",
|
|
@@ -232,9 +211,6 @@ function buildAppCommandOptionMeta(subcommandName = "") {
|
|
|
232
211
|
optionMeta.feature = { inputType: "text" };
|
|
233
212
|
optionMeta["auth-mode"] = { inputType: "text" };
|
|
234
213
|
}
|
|
235
|
-
if (definition.name === "link-local-packages") {
|
|
236
|
-
optionMeta["repo-root"] = { inputType: "text" };
|
|
237
|
-
}
|
|
238
214
|
return optionMeta;
|
|
239
215
|
}
|
|
240
216
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { access
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
4
4
|
|
|
5
5
|
function normalizeText(value = "") {
|
|
6
6
|
return String(value || "").trim();
|
|
@@ -247,6 +247,7 @@ async function runLocalJskitAsync(
|
|
|
247
247
|
appRoot,
|
|
248
248
|
args = [],
|
|
249
249
|
{
|
|
250
|
+
env = {},
|
|
250
251
|
stdout,
|
|
251
252
|
stderr,
|
|
252
253
|
createCliError,
|
|
@@ -262,6 +263,7 @@ async function runLocalJskitAsync(
|
|
|
262
263
|
|
|
263
264
|
return runExternalCommandAsync(localJskitBin, args, {
|
|
264
265
|
cwd: appRoot,
|
|
266
|
+
env,
|
|
265
267
|
stdout,
|
|
266
268
|
stderr,
|
|
267
269
|
quiet,
|
|
@@ -269,141 +271,6 @@ async function runLocalJskitAsync(
|
|
|
269
271
|
});
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
async function resolveLocalRepoRoot({ appRoot = "", explicitRepoRoot = "" } = {}) {
|
|
273
|
-
const explicit = normalizeText(explicitRepoRoot);
|
|
274
|
-
if (explicit) {
|
|
275
|
-
return explicit;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const envRepoRoot = normalizeText(process.env.JSKIT_REPO_ROOT);
|
|
279
|
-
if (envRepoRoot) {
|
|
280
|
-
return envRepoRoot;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let currentDirectory = path.dirname(appRoot);
|
|
284
|
-
while (true) {
|
|
285
|
-
const candidateRoot = path.join(currentDirectory, "jskit-ai");
|
|
286
|
-
if (await fileExists(path.join(candidateRoot, "packages")) && await fileExists(path.join(candidateRoot, "tooling"))) {
|
|
287
|
-
return candidateRoot;
|
|
288
|
-
}
|
|
289
|
-
const parentDirectory = path.dirname(currentDirectory);
|
|
290
|
-
if (parentDirectory === currentDirectory) {
|
|
291
|
-
return "";
|
|
292
|
-
}
|
|
293
|
-
currentDirectory = parentDirectory;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async function discoverLocalPackageMap(repoRoot = "") {
|
|
298
|
-
const packageMap = new Map();
|
|
299
|
-
const parentDirectories = [
|
|
300
|
-
path.join(repoRoot, "packages"),
|
|
301
|
-
path.join(repoRoot, "tooling")
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
for (const parentDirectory of parentDirectories) {
|
|
305
|
-
if (!(await fileExists(parentDirectory))) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const entries = await readdir(parentDirectory, { withFileTypes: true });
|
|
310
|
-
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
311
|
-
|
|
312
|
-
for (const entry of entries) {
|
|
313
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const packageRoot = path.join(parentDirectory, entry.name);
|
|
318
|
-
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
319
|
-
if (!(await fileExists(packageJsonPath))) {
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
let packageJson = {};
|
|
324
|
-
try {
|
|
325
|
-
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
326
|
-
} catch {
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const packageId = normalizeText(packageJson?.name);
|
|
331
|
-
if (!packageId.startsWith("@jskit-ai/")) {
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const packageDirName = packageId.slice("@jskit-ai/".length);
|
|
336
|
-
if (!packageDirName || packageDirName.includes("/")) {
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!packageMap.has(packageDirName)) {
|
|
341
|
-
packageMap.set(packageDirName, packageRoot);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return packageMap;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function linkPackageBinEntries({
|
|
350
|
-
appRoot,
|
|
351
|
-
packageDirName,
|
|
352
|
-
sourceDir,
|
|
353
|
-
stdout
|
|
354
|
-
} = {}) {
|
|
355
|
-
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
356
|
-
if (!(await fileExists(packageJsonPath))) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
let packageJson = {};
|
|
361
|
-
try {
|
|
362
|
-
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
363
|
-
} catch {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const rawBin = packageJson?.bin;
|
|
368
|
-
let binEntries = [];
|
|
369
|
-
if (typeof rawBin === "string") {
|
|
370
|
-
binEntries = [[packageDirName, rawBin]];
|
|
371
|
-
} else if (rawBin && typeof rawBin === "object" && !Array.isArray(rawBin)) {
|
|
372
|
-
binEntries = Object.entries(rawBin);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (binEntries.length < 1) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const binDirectory = path.join(appRoot, "node_modules", ".bin");
|
|
380
|
-
const packageRoot = path.join(appRoot, "node_modules", "@jskit-ai", packageDirName);
|
|
381
|
-
await mkdir(binDirectory, { recursive: true });
|
|
382
|
-
|
|
383
|
-
for (const [rawBinName, rawBinTarget] of binEntries) {
|
|
384
|
-
const binName = normalizeText(rawBinName);
|
|
385
|
-
const binTarget = normalizeText(rawBinTarget);
|
|
386
|
-
if (!binName || !binTarget) {
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const absoluteTarget = path.join(packageRoot, binTarget);
|
|
391
|
-
if (!(await fileExists(absoluteTarget))) {
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const binPath = path.join(binDirectory, binName);
|
|
396
|
-
await rm(binPath, { recursive: true, force: true });
|
|
397
|
-
const relativeTarget = path.relative(binDirectory, absoluteTarget) || absoluteTarget;
|
|
398
|
-
await symlink(relativeTarget, binPath);
|
|
399
|
-
stdout?.write(`[link-local] linked bin ${binName} -> ${relativeTarget}\n`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function resolveSymlinkType() {
|
|
404
|
-
return process.platform === "win32" ? "junction" : "dir";
|
|
405
|
-
}
|
|
406
|
-
|
|
407
274
|
export {
|
|
408
275
|
fileExists,
|
|
409
276
|
normalizeText,
|
|
@@ -414,9 +281,5 @@ export {
|
|
|
414
281
|
formatUtcReleaseTimestamp,
|
|
415
282
|
resolveLocalJskitBin,
|
|
416
283
|
runLocalJskit,
|
|
417
|
-
runLocalJskitAsync
|
|
418
|
-
resolveLocalRepoRoot,
|
|
419
|
-
discoverLocalPackageMap,
|
|
420
|
-
linkPackageBinEntries,
|
|
421
|
-
resolveSymlinkType
|
|
284
|
+
runLocalJskitAsync
|
|
422
285
|
};
|
|
@@ -33,6 +33,8 @@ const DEPENDENCY_SECTIONS = Object.freeze([
|
|
|
33
33
|
})
|
|
34
34
|
]);
|
|
35
35
|
const JSKIT_PACKAGE_PATTERN = /^@jskit-ai\/[a-z0-9._-]+$/iu;
|
|
36
|
+
const JSKIT_CLI_PACKAGE = "@jskit-ai/jskit-cli";
|
|
37
|
+
const UPDATE_PACKAGES_BOOTSTRAPPED_ENV = "JSKIT_UPDATE_PACKAGES_BOOTSTRAPPED";
|
|
36
38
|
const PROGRESS_INTERVAL_MS = 5_000;
|
|
37
39
|
|
|
38
40
|
function collectJskitPackageNames(packageMap = {}) {
|
|
@@ -513,6 +515,68 @@ async function runAppUpdatePackagesCommand(ctx = {}, { appRoot = "", options = {
|
|
|
513
515
|
stdout?.write("[jskit:update] dry-run mode enabled.\n");
|
|
514
516
|
}
|
|
515
517
|
|
|
518
|
+
const cliSection = DEPENDENCY_SECTIONS.find((section) =>
|
|
519
|
+
Object.prototype.hasOwnProperty.call(packageJson?.[section.name] || {}, JSKIT_CLI_PACKAGE)
|
|
520
|
+
);
|
|
521
|
+
if (
|
|
522
|
+
!dryRun &&
|
|
523
|
+
cliSection &&
|
|
524
|
+
process.env[UPDATE_PACKAGES_BOOTSTRAPPED_ENV] !== "1"
|
|
525
|
+
) {
|
|
526
|
+
await resolveLatestVersions([JSKIT_CLI_PACKAGE], latestVersions, {
|
|
527
|
+
appRoot,
|
|
528
|
+
createCliError,
|
|
529
|
+
registryArgs,
|
|
530
|
+
stderr,
|
|
531
|
+
stdout
|
|
532
|
+
});
|
|
533
|
+
const latestCliVersion = latestVersions.get(JSKIT_CLI_PACKAGE);
|
|
534
|
+
let installedCliVersion = "";
|
|
535
|
+
try {
|
|
536
|
+
installedCliVersion = String(
|
|
537
|
+
(await readJson(path.join(appRoot, "node_modules", "@jskit-ai", "jskit-cli", "package.json")))?.version || ""
|
|
538
|
+
).trim();
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (error?.code !== "ENOENT") {
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (installedCliVersion !== latestCliVersion) {
|
|
546
|
+
stdout?.write(`[jskit:update] bootstrapping ${JSKIT_CLI_PACKAGE}@${latestCliVersion}.\n`);
|
|
547
|
+
await runExternalCommandAsync(
|
|
548
|
+
"npm",
|
|
549
|
+
[
|
|
550
|
+
"install",
|
|
551
|
+
...cliSection.installArgs,
|
|
552
|
+
...registryArgs,
|
|
553
|
+
`${JSKIT_CLI_PACKAGE}@${latestCliVersion}`
|
|
554
|
+
],
|
|
555
|
+
{
|
|
556
|
+
cwd: appRoot,
|
|
557
|
+
stdout,
|
|
558
|
+
stderr,
|
|
559
|
+
createCliError
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
stdout?.write("[jskit:update] handing the update to the current local CLI.\n");
|
|
565
|
+
await runLocalJskitAsync(
|
|
566
|
+
appRoot,
|
|
567
|
+
["app", "update-packages", ...registryArgs],
|
|
568
|
+
{
|
|
569
|
+
env: {
|
|
570
|
+
[UPDATE_PACKAGES_BOOTSTRAPPED_ENV]: "1"
|
|
571
|
+
},
|
|
572
|
+
stdout,
|
|
573
|
+
stderr,
|
|
574
|
+
createCliError
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
|
|
516
580
|
await runWithProgress(
|
|
517
581
|
async () => {
|
|
518
582
|
const updatedRootPackages = await updateRootPackages({
|
|
@@ -2,12 +2,8 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
2
2
|
sync: Object.freeze({
|
|
3
3
|
name: "sync",
|
|
4
4
|
summary: "Build the JSKIT web client and sync the Android Capacitor shell.",
|
|
5
|
-
usage: "jskit mobile android sync [--dry-run]
|
|
5
|
+
usage: "jskit mobile android sync [--dry-run]",
|
|
6
6
|
options: Object.freeze([
|
|
7
|
-
Object.freeze({
|
|
8
|
-
label: "--devlinks",
|
|
9
|
-
description: "If sync needs to run npm install first, also run npm run --if-present devlinks afterward."
|
|
10
|
-
}),
|
|
11
7
|
Object.freeze({
|
|
12
8
|
label: "--dry-run",
|
|
13
9
|
description: "Preview the build and Capacitor sync commands without writing dist output or mutating the Android shell."
|
|
@@ -134,18 +134,11 @@ async function runLocalProjectBinary(binaryName, args = [], {
|
|
|
134
134
|
|
|
135
135
|
async function installAppDependenciesForHook({
|
|
136
136
|
appRoot,
|
|
137
|
-
appPackageJson,
|
|
138
137
|
io,
|
|
139
138
|
pathModule = path,
|
|
140
139
|
createCliError,
|
|
141
|
-
dryRun = false
|
|
142
|
-
runDevlinks = false
|
|
140
|
+
dryRun = false
|
|
143
141
|
} = {}) {
|
|
144
|
-
const packageScripts =
|
|
145
|
-
appPackageJson?.scripts && typeof appPackageJson.scripts === "object"
|
|
146
|
-
? appPackageJson.scripts
|
|
147
|
-
: {};
|
|
148
|
-
|
|
149
142
|
await runLocalProjectBinary("npm", ["install"], {
|
|
150
143
|
appRoot,
|
|
151
144
|
io,
|
|
@@ -154,17 +147,6 @@ async function installAppDependenciesForHook({
|
|
|
154
147
|
explanation: "[mobile] Installing app dependencies for the mobile shell:",
|
|
155
148
|
dryRun
|
|
156
149
|
});
|
|
157
|
-
|
|
158
|
-
if (runDevlinks === true && Object.prototype.hasOwnProperty.call(packageScripts, "devlinks")) {
|
|
159
|
-
await runLocalProjectBinary("npm", ["run", "--if-present", "devlinks"], {
|
|
160
|
-
appRoot,
|
|
161
|
-
io,
|
|
162
|
-
pathModule,
|
|
163
|
-
createCliError,
|
|
164
|
-
explanation: "[mobile] Refreshing local JSKIT package links:",
|
|
165
|
-
dryRun
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
150
|
}
|
|
169
151
|
|
|
170
152
|
async function resolvePackageOptionInputForInstall({
|
|
@@ -248,8 +230,7 @@ function createInstallHookHelpers({
|
|
|
248
230
|
ctx,
|
|
249
231
|
appRoot,
|
|
250
232
|
io,
|
|
251
|
-
appPackageJson
|
|
252
|
-
commandOptions = {}
|
|
233
|
+
appPackageJson
|
|
253
234
|
} = {}) {
|
|
254
235
|
return Object.freeze({
|
|
255
236
|
ensureManagedMobileConfig: async ({ dryRun = false } = {}) =>
|
|
@@ -263,12 +244,10 @@ function createInstallHookHelpers({
|
|
|
263
244
|
installAppDependencies: async ({ dryRun = false } = {}) =>
|
|
264
245
|
await installAppDependenciesForHook({
|
|
265
246
|
appRoot,
|
|
266
|
-
appPackageJson,
|
|
267
247
|
io,
|
|
268
248
|
pathModule: ctx.path,
|
|
269
249
|
createCliError: ctx.createCliError,
|
|
270
|
-
dryRun
|
|
271
|
-
runDevlinks: commandOptions.devlinks === true
|
|
250
|
+
dryRun
|
|
272
251
|
}),
|
|
273
252
|
runProjectBinary: async (binaryName, args = [], { dryRun = false, explanation = "" } = {}) =>
|
|
274
253
|
await runLocalProjectBinary(binaryName, args, {
|
|
@@ -649,8 +628,7 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
649
628
|
ctx,
|
|
650
629
|
appRoot,
|
|
651
630
|
io,
|
|
652
|
-
appPackageJson: packageJson
|
|
653
|
-
commandOptions: options
|
|
631
|
+
appPackageJson: packageJson
|
|
654
632
|
});
|
|
655
633
|
|
|
656
634
|
for (const packageId of packagesToInstall) {
|
|
@@ -14,7 +14,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
14
14
|
options: {
|
|
15
15
|
dryRun: false,
|
|
16
16
|
runNpmInstall: false,
|
|
17
|
-
devlinks: false,
|
|
18
17
|
full: false,
|
|
19
18
|
expanded: false,
|
|
20
19
|
details: false,
|
|
@@ -41,7 +40,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
41
40
|
const options = {
|
|
42
41
|
dryRun: false,
|
|
43
42
|
runNpmInstall: false,
|
|
44
|
-
devlinks: false,
|
|
45
43
|
full: false,
|
|
46
44
|
expanded: false,
|
|
47
45
|
details: false,
|
|
@@ -73,10 +71,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
73
71
|
options.runNpmInstall = true;
|
|
74
72
|
continue;
|
|
75
73
|
}
|
|
76
|
-
if (token === "--devlinks") {
|
|
77
|
-
options.devlinks = true;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
74
|
if (token === "--full") {
|
|
81
75
|
options.full = true;
|
|
82
76
|
continue;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const OPTION_FLAG_LABELS = Object.freeze({
|
|
2
2
|
dryRun: "--dry-run",
|
|
3
3
|
runNpmInstall: "--run-npm-install",
|
|
4
|
-
devlinks: "--devlinks",
|
|
5
4
|
full: "--full",
|
|
6
5
|
expanded: "--expanded",
|
|
7
6
|
details: "--details",
|
|
@@ -136,7 +135,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
136
135
|
parameters: Object.freeze([
|
|
137
136
|
Object.freeze({
|
|
138
137
|
name: "<subcommand>",
|
|
139
|
-
description: "verify | update-packages |
|
|
138
|
+
description: "verify | update-packages | release | adopt-managed-scripts | migrate-source-mutations."
|
|
140
139
|
})
|
|
141
140
|
]),
|
|
142
141
|
defaults: Object.freeze([
|
|
@@ -189,13 +188,12 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
189
188
|
defaults: Object.freeze([
|
|
190
189
|
"Install the shell first with jskit add package @jskit-ai/mobile-capacitor.",
|
|
191
190
|
"Use jskit mobile <platform> help for platform-specific usage.",
|
|
192
|
-
"--dry-run is accepted by jskit mobile android sync/run/build."
|
|
193
|
-
"--devlinks runs npm run --if-present devlinks after jskit mobile android sync maintenance for development-only relinking."
|
|
191
|
+
"--dry-run is accepted by jskit mobile android sync/run/build."
|
|
194
192
|
]),
|
|
195
193
|
fullUse: "jskit mobile <platform> <subcommand> [help] [--dry-run] [--<option> <value>...]",
|
|
196
194
|
showHelpOnBareInvocation: true,
|
|
197
195
|
handlerName: "commandMobile",
|
|
198
|
-
allowedFlagKeys: Object.freeze(["dryRun"
|
|
196
|
+
allowedFlagKeys: Object.freeze(["dryRun"]),
|
|
199
197
|
inlineOptionMode: "delegate",
|
|
200
198
|
allowedValueOptionNames: Object.freeze([]),
|
|
201
199
|
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
@@ -298,14 +296,13 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
298
296
|
"No npm install runs unless --run-npm-install is passed.",
|
|
299
297
|
"Short ids resolve to @jskit-ai/<id> when available.",
|
|
300
298
|
"Running without args lists bundles and runtime packages.",
|
|
301
|
-
"Existing matching version is skipped unless options force reapply."
|
|
302
|
-
"--devlinks runs npm run --if-present devlinks after install when the app defines that script."
|
|
299
|
+
"Existing matching version is skipped unless options force reapply."
|
|
303
300
|
]),
|
|
304
301
|
fullUse:
|
|
305
|
-
"jskit add <package|bundle> <id> [--<option> <value>...] [--dry-run] [--run-npm-install] [--
|
|
302
|
+
"jskit add <package|bundle> <id> [--<option> <value>...] [--dry-run] [--run-npm-install] [--json] [--verbose]",
|
|
306
303
|
showHelpOnBareInvocation: false,
|
|
307
304
|
handlerName: "commandAdd",
|
|
308
|
-
allowedFlagKeys: Object.freeze(["dryRun", "runNpmInstall", "
|
|
305
|
+
allowedFlagKeys: Object.freeze(["dryRun", "runNpmInstall", "json", "verbose"]),
|
|
309
306
|
inlineOptionMode: "delegate",
|
|
310
307
|
allowedValueOptionNames: Object.freeze([]),
|
|
311
308
|
canDelegateInlineOptions: canDelegateAddInlineOptions
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const DEFAULT_LOCAL_BASE_URL = "http://127.0.0.1:4173";
|
|
2
|
+
const DEFAULT_SMOKE_PATH = "/home";
|
|
3
|
+
const DEFAULT_VIEWPORTS = Object.freeze([
|
|
4
|
+
Object.freeze({ name: "compact", width: 390, height: 844 }),
|
|
5
|
+
Object.freeze({ name: "medium", width: 768, height: 1024 }),
|
|
6
|
+
Object.freeze({ name: "expanded", width: 1280, height: 900 })
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function normalizeBaseUrl(value) {
|
|
10
|
+
return String(value || "").trim().replace(/\/+$/u, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createJskitPlaywrightConfig({ env = process.env } = {}) {
|
|
14
|
+
const managedBaseUrl = normalizeBaseUrl(env.PLAYWRIGHT_BASE_URL);
|
|
15
|
+
const baseURL = managedBaseUrl || DEFAULT_LOCAL_BASE_URL;
|
|
16
|
+
const storageState = String(env.JSKIT_PLAYWRIGHT_STORAGE_STATE || "").trim();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
testDir: "./tests/e2e",
|
|
20
|
+
timeout: 60_000,
|
|
21
|
+
expect: {
|
|
22
|
+
timeout: 10_000
|
|
23
|
+
},
|
|
24
|
+
use: {
|
|
25
|
+
baseURL,
|
|
26
|
+
headless: true,
|
|
27
|
+
...(storageState ? { storageState } : {})
|
|
28
|
+
},
|
|
29
|
+
...(managedBaseUrl ? {} : {
|
|
30
|
+
webServer: {
|
|
31
|
+
command: "npm run build && node ./bin/server.js",
|
|
32
|
+
env: {
|
|
33
|
+
PORT: "4173"
|
|
34
|
+
},
|
|
35
|
+
url: `${baseURL}/api/health`,
|
|
36
|
+
reuseExistingServer: true,
|
|
37
|
+
timeout: 180_000
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function expectNoHorizontalOverflow(page, expect) {
|
|
44
|
+
const metrics = await page.evaluate(() => ({
|
|
45
|
+
clientWidth: document.documentElement.clientWidth,
|
|
46
|
+
scrollWidth: document.documentElement.scrollWidth
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function expectVisibleTapTargets(page, expect) {
|
|
53
|
+
const heights = await page.locator("a[href], button, [role='button'], .v-btn, .v-list-item").evaluateAll(
|
|
54
|
+
(elements) => elements
|
|
55
|
+
.filter((element) => {
|
|
56
|
+
const rect = element.getBoundingClientRect();
|
|
57
|
+
const style = window.getComputedStyle(element);
|
|
58
|
+
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0;
|
|
59
|
+
})
|
|
60
|
+
.map((element) => element.getBoundingClientRect().height)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
for (const height of heights) {
|
|
64
|
+
expect(height).toBeGreaterThanOrEqual(48);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runGeneratedAppSmoke({ test, expect, expectedText } = {}) {
|
|
69
|
+
if (!test || !expect || !String(expectedText || "").trim()) {
|
|
70
|
+
throw new Error("runGeneratedAppSmoke requires Playwright test, expect, and expectedText.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const smokePath = String(process.env.JSKIT_PLAYWRIGHT_SMOKE_PATH || DEFAULT_SMOKE_PATH);
|
|
74
|
+
|
|
75
|
+
test.describe("generated app responsive smoke", () => {
|
|
76
|
+
for (const viewport of DEFAULT_VIEWPORTS) {
|
|
77
|
+
test(`${viewport.name} home route renders without horizontal overflow`, async ({ page }) => {
|
|
78
|
+
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
79
|
+
await page.goto(smokePath);
|
|
80
|
+
await expect(page.locator("body")).toBeVisible();
|
|
81
|
+
await expect(page.getByText(expectedText)).toBeVisible();
|
|
82
|
+
await expect(page.locator(".generated-ui-screen").first()).toBeVisible();
|
|
83
|
+
await expectVisibleTapTargets(page, expect);
|
|
84
|
+
await expectNoHorizontalOverflow(page, expect);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
createJskitPlaywrightConfig,
|
|
92
|
+
DEFAULT_LOCAL_BASE_URL,
|
|
93
|
+
DEFAULT_VIEWPORTS,
|
|
94
|
+
runGeneratedAppSmoke
|
|
95
|
+
};
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { lstat, mkdir, readFile, readlink, rm, symlink } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
discoverLocalPackageMap,
|
|
5
|
-
fileExists,
|
|
6
|
-
linkPackageBinEntries,
|
|
7
|
-
resolveLocalRepoRoot,
|
|
8
|
-
resolveSymlinkType
|
|
9
|
-
} from "./shared.js";
|
|
10
|
-
|
|
11
|
-
const COMPANION_PACKAGES = Object.freeze([
|
|
12
|
-
Object.freeze({
|
|
13
|
-
packageName: "json-rest-schema",
|
|
14
|
-
repoDirName: "json-rest-schema"
|
|
15
|
-
}),
|
|
16
|
-
Object.freeze({
|
|
17
|
-
packageName: "json-rest-stores",
|
|
18
|
-
repoDirName: "json-rest-stores"
|
|
19
|
-
})
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
function collectDeclaredPackageNames(packageJson = {}) {
|
|
23
|
-
const names = new Set();
|
|
24
|
-
const sections = [
|
|
25
|
-
packageJson?.dependencies,
|
|
26
|
-
packageJson?.devDependencies,
|
|
27
|
-
packageJson?.optionalDependencies,
|
|
28
|
-
packageJson?.peerDependencies
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
for (const section of sections) {
|
|
32
|
-
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const packageName of Object.keys(section)) {
|
|
37
|
-
const normalizedPackageName = String(packageName || "").trim();
|
|
38
|
-
if (normalizedPackageName) {
|
|
39
|
-
names.add(normalizedPackageName);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return names;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function verifySymlinkTarget(targetPath = "", sourceDir = "", {
|
|
48
|
-
packageName = ""
|
|
49
|
-
} = {}) {
|
|
50
|
-
const stats = await lstat(targetPath);
|
|
51
|
-
if (!stats.isSymbolicLink()) {
|
|
52
|
-
throw new Error(`[link-local] expected ${packageName || targetPath} to be a symlink after linking.`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const linkedTarget = await readlink(targetPath);
|
|
56
|
-
if (linkedTarget !== sourceDir) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`[link-local] expected ${packageName || targetPath} to link to ${sourceDir}, got ${linkedTarget}.`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function replaceWithSymlink(targetPath = "", sourceDir = "", {
|
|
64
|
-
packageName = ""
|
|
65
|
-
} = {}) {
|
|
66
|
-
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
67
|
-
await rm(targetPath, { recursive: true, force: true });
|
|
68
|
-
await symlink(sourceDir, targetPath, resolveSymlinkType());
|
|
69
|
-
await verifySymlinkTarget(targetPath, sourceDir, {
|
|
70
|
-
packageName
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function maybeLinkCompanionPackages({
|
|
75
|
-
appRoot = "",
|
|
76
|
-
repoRoot = "",
|
|
77
|
-
stdout,
|
|
78
|
-
createCliError
|
|
79
|
-
}) {
|
|
80
|
-
const companionRoot = path.dirname(repoRoot);
|
|
81
|
-
const appPackageJsonPath = path.join(appRoot, "package.json");
|
|
82
|
-
let appPackageJson = {};
|
|
83
|
-
try {
|
|
84
|
-
appPackageJson = JSON.parse(await readFile(appPackageJsonPath, "utf8"));
|
|
85
|
-
} catch {
|
|
86
|
-
appPackageJson = {};
|
|
87
|
-
}
|
|
88
|
-
const declaredPackageNames = collectDeclaredPackageNames(appPackageJson);
|
|
89
|
-
let linkedCount = 0;
|
|
90
|
-
|
|
91
|
-
for (const companion of COMPANION_PACKAGES) {
|
|
92
|
-
if (!declaredPackageNames.has(companion.packageName)) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const sourceDir = path.join(companionRoot, companion.repoDirName);
|
|
97
|
-
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
98
|
-
if (!(await fileExists(packageJsonPath))) {
|
|
99
|
-
throw createCliError(
|
|
100
|
-
`[link-local] companion package ${companion.packageName} is declared by the app but local source was not found at ${sourceDir}.`,
|
|
101
|
-
{ exitCode: 1 }
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let packageJson = {};
|
|
106
|
-
try {
|
|
107
|
-
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
108
|
-
} catch {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (String(packageJson?.name || "").trim() !== companion.packageName) {
|
|
113
|
-
throw createCliError(
|
|
114
|
-
`[link-local] companion source at ${sourceDir} does not match expected package ${companion.packageName}.`,
|
|
115
|
-
{ exitCode: 1 }
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const appTargetPath = path.join(appRoot, "node_modules", companion.packageName);
|
|
120
|
-
await replaceWithSymlink(appTargetPath, sourceDir, {
|
|
121
|
-
packageName: companion.packageName
|
|
122
|
-
});
|
|
123
|
-
stdout.write(`[link-local] linked ${companion.packageName} -> ${sourceDir}\n`);
|
|
124
|
-
linkedCount += 1;
|
|
125
|
-
|
|
126
|
-
const repoTargetPath = path.join(repoRoot, "node_modules", companion.packageName);
|
|
127
|
-
await replaceWithSymlink(repoTargetPath, sourceDir, {
|
|
128
|
-
packageName: `${companion.packageName} (repo root)`
|
|
129
|
-
});
|
|
130
|
-
stdout.write(`[link-local] linked repo companion ${companion.packageName} -> ${sourceDir}\n`);
|
|
131
|
-
linkedCount += 1;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return linkedCount;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function runAppLinkLocalPackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout }) {
|
|
138
|
-
const { createCliError } = ctx;
|
|
139
|
-
const explicitRepoRoot = String(options?.inlineOptions?.["repo-root"] || "").trim();
|
|
140
|
-
const scopeDirectory = path.join(appRoot, "node_modules", "@jskit-ai");
|
|
141
|
-
const viteCacheDirectory = path.join(appRoot, "node_modules", ".vite");
|
|
142
|
-
|
|
143
|
-
if (!(await fileExists(scopeDirectory))) {
|
|
144
|
-
throw createCliError(`[link-local] @jskit-ai scope not found at ${scopeDirectory} (run npm install first).`, {
|
|
145
|
-
exitCode: 1
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const repoRoot = await resolveLocalRepoRoot({ appRoot, explicitRepoRoot });
|
|
150
|
-
if (!repoRoot) {
|
|
151
|
-
throw createCliError("[link-local] no JSKIT repository found. Set JSKIT_REPO_ROOT or use --repo-root.", {
|
|
152
|
-
exitCode: 1
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!(await fileExists(path.join(repoRoot, "packages"))) || !(await fileExists(path.join(repoRoot, "tooling")))) {
|
|
157
|
-
throw createCliError(`[link-local] JSKIT repo root is not valid: ${repoRoot}`, {
|
|
158
|
-
exitCode: 1
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const packageMap = await discoverLocalPackageMap(repoRoot);
|
|
163
|
-
let linkedCount = 0;
|
|
164
|
-
await mkdir(scopeDirectory, { recursive: true });
|
|
165
|
-
|
|
166
|
-
for (const [packageDirName, sourceDir] of [...packageMap.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
167
|
-
const targetPath = path.join(scopeDirectory, packageDirName);
|
|
168
|
-
await replaceWithSymlink(targetPath, sourceDir, {
|
|
169
|
-
packageName: `@jskit-ai/${packageDirName}`
|
|
170
|
-
});
|
|
171
|
-
stdout.write(`[link-local] linked @jskit-ai/${packageDirName} -> ${sourceDir}\n`);
|
|
172
|
-
await linkPackageBinEntries({
|
|
173
|
-
appRoot,
|
|
174
|
-
packageDirName,
|
|
175
|
-
sourceDir,
|
|
176
|
-
stdout
|
|
177
|
-
});
|
|
178
|
-
linkedCount += 1;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
linkedCount += await maybeLinkCompanionPackages({
|
|
182
|
-
appRoot,
|
|
183
|
-
repoRoot,
|
|
184
|
-
stdout,
|
|
185
|
-
createCliError
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (await fileExists(viteCacheDirectory)) {
|
|
189
|
-
await rm(viteCacheDirectory, { recursive: true, force: true });
|
|
190
|
-
stdout.write(`[link-local] cleared Vite cache at ${viteCacheDirectory}\n`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
stdout.write(`[link-local] done. linked=${linkedCount}\n`);
|
|
194
|
-
return 0;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export { runAppLinkLocalPackagesCommand };
|