@jskit-ai/jskit-cli 0.2.72 → 0.2.74
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/completion.js +3 -1
- package/src/server/cliRuntime/descriptorValidation.js +52 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +18 -14
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +10 -5
- package/src/server/cliRuntime/mutations/textMutations.js +17 -5
- package/src/server/cliRuntime/packageInstallFlow.js +42 -19
- package/src/server/cliRuntime/viteProxy.js +25 -7
- package/src/server/commandHandlers/health.js +88 -15
- package/src/server/commandHandlers/mobile.js +1316 -0
- package/src/server/commandHandlers/mobileCommandCatalog.js +196 -0
- package/src/server/commandHandlers/mobileShellSupport.js +929 -0
- package/src/server/commandHandlers/packageCommands/add.js +415 -2
- package/src/server/commandHandlers/packageCommands/migrations.js +2 -1
- package/src/server/core/argParser.js +6 -0
- package/src/server/core/commandCatalog.js +31 -3
- package/src/server/core/createCommandHandlers.js +3 -0
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { importFreshModuleFromAbsolutePath } from "@jskit-ai/kernel/server/support";
|
|
1
4
|
import {
|
|
2
5
|
ensureArray,
|
|
3
6
|
ensureObject,
|
|
4
7
|
sortStrings
|
|
5
8
|
} from "../../shared/collectionUtils.js";
|
|
9
|
+
import {
|
|
10
|
+
fileExists
|
|
11
|
+
} from "../appCommands/shared.js";
|
|
12
|
+
import {
|
|
13
|
+
ensureMobileConfigStub,
|
|
14
|
+
collectCapacitorShellInstallIssues,
|
|
15
|
+
ensureAndroidManifestDeepLinks,
|
|
16
|
+
ensureAndroidNativeShellIdentity
|
|
17
|
+
} from "../mobileShellSupport.js";
|
|
6
18
|
import {
|
|
7
19
|
isHelpToken,
|
|
8
20
|
renderAddCatalogHelp,
|
|
@@ -13,6 +25,7 @@ import {
|
|
|
13
25
|
ensureLocalMainPlacementComponentProvisioning,
|
|
14
26
|
resolveProvisionableLocalPlacementComponentTokens
|
|
15
27
|
} from "./tabLinkItemProvisioning.js";
|
|
28
|
+
import { resolvePackageTemplateRoot } from "../../cliRuntime/packageTemplateResolution.js";
|
|
16
29
|
|
|
17
30
|
const COMPONENT_TOKEN_PATTERN = /\bcomponentToken\s*:\s*["']([^"']+)["']/g;
|
|
18
31
|
|
|
@@ -35,6 +48,262 @@ function collectPlacementComponentTokensFromManagedRecords(installedPackageRecor
|
|
|
35
48
|
return sortStrings([...collectedTokens]);
|
|
36
49
|
}
|
|
37
50
|
|
|
51
|
+
function renderWrappedShellCommand(binaryName, args = [], {
|
|
52
|
+
maxWidth = 100,
|
|
53
|
+
continuationIndent = " "
|
|
54
|
+
} = {}) {
|
|
55
|
+
const tokens = [String(binaryName || "").trim(), ...ensureArray(args).map((entry) => String(entry || "").trim()).filter(Boolean)];
|
|
56
|
+
if (tokens.length < 1 || !tokens[0]) {
|
|
57
|
+
return "$";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let currentLine = "$";
|
|
61
|
+
const renderedLines = [];
|
|
62
|
+
for (const token of tokens) {
|
|
63
|
+
const prefix = currentLine === "$" ? " " : " ";
|
|
64
|
+
if ((`${currentLine}${prefix}${token}`).length <= maxWidth || currentLine === "$") {
|
|
65
|
+
currentLine = `${currentLine}${prefix}${token}`;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
renderedLines.push(`${currentLine} \\`);
|
|
70
|
+
currentLine = `${continuationIndent}${token}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
renderedLines.push(currentLine);
|
|
74
|
+
return renderedLines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function runLocalProjectBinary(binaryName, args = [], {
|
|
78
|
+
appRoot,
|
|
79
|
+
io,
|
|
80
|
+
pathModule = path,
|
|
81
|
+
createCliError,
|
|
82
|
+
explanation = "",
|
|
83
|
+
dryRun = false
|
|
84
|
+
} = {}) {
|
|
85
|
+
const renderedArgs = Array.isArray(args) ? args.join(" ") : "";
|
|
86
|
+
if (explanation) {
|
|
87
|
+
io?.stdout?.write(`${explanation}\n`);
|
|
88
|
+
io?.stdout?.write(`${renderWrappedShellCommand(binaryName, args)}\n`);
|
|
89
|
+
}
|
|
90
|
+
if (dryRun === true) {
|
|
91
|
+
io?.stdout?.write(`[dry-run] ${binaryName}${renderedArgs ? ` ${renderedArgs}` : ""}\n`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const localBinDirectory = pathModule.join(appRoot, "node_modules", ".bin");
|
|
96
|
+
const inheritedPath = String(process.env.PATH || "");
|
|
97
|
+
const spawnedEnv = {
|
|
98
|
+
...process.env,
|
|
99
|
+
PATH: `${localBinDirectory}${pathModule.delimiter}${inheritedPath}`
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await new Promise((resolve, reject) => {
|
|
103
|
+
const child = spawn(binaryName, Array.isArray(args) ? args : [], {
|
|
104
|
+
cwd: appRoot,
|
|
105
|
+
env: spawnedEnv,
|
|
106
|
+
stdio: "inherit"
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
child.on("error", (error) => {
|
|
110
|
+
if (error?.code === "ENOENT") {
|
|
111
|
+
reject(
|
|
112
|
+
createCliError(
|
|
113
|
+
`Could not find local "${binaryName}" in node_modules/.bin. Re-run the package install after dependencies are installed.`
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
reject(error);
|
|
119
|
+
});
|
|
120
|
+
child.on("exit", (code) => {
|
|
121
|
+
if (code === 0) {
|
|
122
|
+
resolve();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
reject(createCliError(`${binaryName} ${args.join(" ")} failed with exit code ${code}.`));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function installAppDependenciesForHook({
|
|
131
|
+
appRoot,
|
|
132
|
+
appPackageJson,
|
|
133
|
+
io,
|
|
134
|
+
pathModule = path,
|
|
135
|
+
createCliError,
|
|
136
|
+
dryRun = false,
|
|
137
|
+
runDevlinks = false
|
|
138
|
+
} = {}) {
|
|
139
|
+
const packageScripts =
|
|
140
|
+
appPackageJson?.scripts && typeof appPackageJson.scripts === "object"
|
|
141
|
+
? appPackageJson.scripts
|
|
142
|
+
: {};
|
|
143
|
+
|
|
144
|
+
await runLocalProjectBinary("npm", ["install"], {
|
|
145
|
+
appRoot,
|
|
146
|
+
io,
|
|
147
|
+
pathModule,
|
|
148
|
+
createCliError,
|
|
149
|
+
explanation: "[mobile] Installing app dependencies for the mobile shell:",
|
|
150
|
+
dryRun
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (runDevlinks === true && Object.prototype.hasOwnProperty.call(packageScripts, "devlinks")) {
|
|
154
|
+
await runLocalProjectBinary("npm", ["run", "--if-present", "devlinks"], {
|
|
155
|
+
appRoot,
|
|
156
|
+
io,
|
|
157
|
+
pathModule,
|
|
158
|
+
createCliError,
|
|
159
|
+
explanation: "[mobile] Refreshing local JSKIT package links:",
|
|
160
|
+
dryRun
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function validateHookResult(result = {}, { packageId = "", hookLabel = "" } = {}) {
|
|
166
|
+
if (typeof result === "undefined" || result === null) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
if (typeof result !== "object" || Array.isArray(result)) {
|
|
170
|
+
throw new Error(`${packageId} ${hookLabel} must return an object when it returns a value.`);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function loadInstallHook({
|
|
176
|
+
packageEntry,
|
|
177
|
+
appRoot,
|
|
178
|
+
hookSpec,
|
|
179
|
+
hookLabel = ""
|
|
180
|
+
} = {}) {
|
|
181
|
+
const entrypoint = String(hookSpec?.entrypoint || "").trim();
|
|
182
|
+
const exportName = String(hookSpec?.export || "").trim() || "default";
|
|
183
|
+
if (!entrypoint) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const templateRoot = await resolvePackageTemplateRoot({
|
|
188
|
+
packageEntry,
|
|
189
|
+
appRoot
|
|
190
|
+
});
|
|
191
|
+
const absoluteEntrypointPath = path.resolve(templateRoot, entrypoint);
|
|
192
|
+
if (!(await fileExists(absoluteEntrypointPath))) {
|
|
193
|
+
throw new Error(`${packageEntry.packageId} ${hookLabel} entrypoint not found at ${entrypoint}.`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let moduleNamespace = null;
|
|
197
|
+
try {
|
|
198
|
+
moduleNamespace = await importFreshModuleFromAbsolutePath(absoluteEntrypointPath);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Unable to load ${hookLabel} entrypoint ${entrypoint} for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const handler = exportName === "default" ? moduleNamespace?.default : moduleNamespace?.[exportName];
|
|
206
|
+
if (typeof handler !== "function") {
|
|
207
|
+
throw new Error(`${packageEntry.packageId} ${hookLabel} export "${exportName}" is not a function.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return handler;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createInstallHookHelpers({
|
|
214
|
+
ctx,
|
|
215
|
+
appRoot,
|
|
216
|
+
io,
|
|
217
|
+
appPackageJson,
|
|
218
|
+
commandOptions = {}
|
|
219
|
+
} = {}) {
|
|
220
|
+
return Object.freeze({
|
|
221
|
+
ensureManagedMobileConfig: async ({ dryRun = false } = {}) =>
|
|
222
|
+
await ensureMobileConfigStub({
|
|
223
|
+
ctx,
|
|
224
|
+
appRoot,
|
|
225
|
+
packageJson: appPackageJson,
|
|
226
|
+
dryRun,
|
|
227
|
+
stdout: io?.stdout
|
|
228
|
+
}),
|
|
229
|
+
installAppDependencies: async ({ dryRun = false } = {}) =>
|
|
230
|
+
await installAppDependenciesForHook({
|
|
231
|
+
appRoot,
|
|
232
|
+
appPackageJson,
|
|
233
|
+
io,
|
|
234
|
+
pathModule: ctx.path,
|
|
235
|
+
createCliError: ctx.createCliError,
|
|
236
|
+
dryRun,
|
|
237
|
+
runDevlinks: commandOptions.devlinks === true
|
|
238
|
+
}),
|
|
239
|
+
runProjectBinary: async (binaryName, args = [], { dryRun = false, explanation = "" } = {}) =>
|
|
240
|
+
await runLocalProjectBinary(binaryName, args, {
|
|
241
|
+
appRoot,
|
|
242
|
+
io,
|
|
243
|
+
pathModule: ctx.path,
|
|
244
|
+
createCliError: ctx.createCliError,
|
|
245
|
+
explanation,
|
|
246
|
+
dryRun
|
|
247
|
+
}),
|
|
248
|
+
collectCapacitorShellInstallIssues: async () =>
|
|
249
|
+
await collectCapacitorShellInstallIssues({
|
|
250
|
+
ctx,
|
|
251
|
+
appRoot
|
|
252
|
+
}),
|
|
253
|
+
ensureAndroidManifestDeepLinks: async ({ dryRun = false } = {}) =>
|
|
254
|
+
await ensureAndroidManifestDeepLinks({
|
|
255
|
+
ctx,
|
|
256
|
+
appRoot,
|
|
257
|
+
dryRun,
|
|
258
|
+
stdout: io?.stdout
|
|
259
|
+
}),
|
|
260
|
+
ensureAndroidNativeShellIdentity: async ({ dryRun = false } = {}) =>
|
|
261
|
+
await ensureAndroidNativeShellIdentity({
|
|
262
|
+
ctx,
|
|
263
|
+
appRoot,
|
|
264
|
+
dryRun,
|
|
265
|
+
stdout: io?.stdout
|
|
266
|
+
}),
|
|
267
|
+
fileExists
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function invokeInstallHook({
|
|
272
|
+
packageEntry,
|
|
273
|
+
appRoot,
|
|
274
|
+
hookSpec,
|
|
275
|
+
hookLabel,
|
|
276
|
+
hookContext,
|
|
277
|
+
createCliError
|
|
278
|
+
} = {}) {
|
|
279
|
+
if (!hookSpec || Object.keys(ensureObject(hookSpec)).length < 1) {
|
|
280
|
+
return {};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const handler = await loadInstallHook({
|
|
284
|
+
packageEntry,
|
|
285
|
+
appRoot,
|
|
286
|
+
hookSpec,
|
|
287
|
+
hookLabel
|
|
288
|
+
});
|
|
289
|
+
if (!handler) {
|
|
290
|
+
return {};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let result = null;
|
|
294
|
+
try {
|
|
295
|
+
result = await handler(hookContext);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw createCliError(
|
|
298
|
+
`${packageEntry.packageId} ${hookLabel} failed: ${String(error?.message || error || "unknown error")}`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return validateHookResult(result, {
|
|
302
|
+
packageId: packageEntry.packageId,
|
|
303
|
+
hookLabel
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
38
307
|
async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io }) {
|
|
39
308
|
const {
|
|
40
309
|
createCliError,
|
|
@@ -264,6 +533,7 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
264
533
|
|
|
265
534
|
const packagesToInstall = [];
|
|
266
535
|
const resolvedOptionsByPackage = {};
|
|
536
|
+
const installReasonByPackage = {};
|
|
267
537
|
const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
|
|
268
538
|
enabled: options.json !== true
|
|
269
539
|
});
|
|
@@ -274,6 +544,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
274
544
|
const existingInstall = ensureObject(lock.installedPackages[packageId]);
|
|
275
545
|
const existingVersion = String(existingInstall.version || "").trim();
|
|
276
546
|
const isDirectTargetPackage = targetType === "package" && packageId === resolvedTargetPackageId;
|
|
547
|
+
const hasInstallLifecycleHooks =
|
|
548
|
+
Object.keys(ensureObject(ensureObject(ensureObject(packageEntry.descriptor).lifecycle).install)).length > 0;
|
|
277
549
|
const packageInlineOptions = targetType === "bundle"
|
|
278
550
|
? resolveBundleInlineOptionsForPackage(packageEntry, options.inlineOptions)
|
|
279
551
|
: isDirectTargetPackage
|
|
@@ -282,6 +554,7 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
282
554
|
const hasPackageInlineOptions = Object.keys(packageInlineOptions).length > 0;
|
|
283
555
|
const shouldReapplyInstalledPackage =
|
|
284
556
|
(isDirectTargetPackage && (forceReapplyTarget || hasInlineOptions)) ||
|
|
557
|
+
(isDirectTargetPackage && invocationMode === "add" && hasInstallLifecycleHooks && Boolean(existingVersion)) ||
|
|
285
558
|
(targetType === "bundle" && hasPackageInlineOptions);
|
|
286
559
|
const shouldSkipGenerateDependencyReinstall =
|
|
287
560
|
invocationMode === "generate" &&
|
|
@@ -294,6 +567,11 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
294
567
|
continue;
|
|
295
568
|
}
|
|
296
569
|
packagesToInstall.push(packageId);
|
|
570
|
+
installReasonByPackage[packageId] = !existingVersion
|
|
571
|
+
? "install"
|
|
572
|
+
: existingVersion === packageEntry.version
|
|
573
|
+
? "reapply"
|
|
574
|
+
: "upgrade";
|
|
297
575
|
const lockEntryOptions = ensureObject(existingInstall.options);
|
|
298
576
|
resolvedOptionsByPackage[packageId] = await resolvePackageOptions(
|
|
299
577
|
packageEntry,
|
|
@@ -308,6 +586,95 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
308
586
|
|
|
309
587
|
const touchedFiles = new Set();
|
|
310
588
|
const installedPackageRecords = [];
|
|
589
|
+
const prepareHookWarnings = [];
|
|
590
|
+
const installHookHelpers = createInstallHookHelpers({
|
|
591
|
+
ctx,
|
|
592
|
+
appRoot,
|
|
593
|
+
io,
|
|
594
|
+
appPackageJson: packageJson,
|
|
595
|
+
commandOptions: options
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
for (const packageId of packagesToInstall) {
|
|
599
|
+
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
600
|
+
const hookResult = await invokeInstallHook({
|
|
601
|
+
packageEntry,
|
|
602
|
+
appRoot,
|
|
603
|
+
hookSpec: ensureObject(ensureObject(ensureObject(packageEntry.descriptor).lifecycle).install).prepare,
|
|
604
|
+
hookLabel: "lifecycle.install.prepare",
|
|
605
|
+
createCliError,
|
|
606
|
+
hookContext: {
|
|
607
|
+
appRoot,
|
|
608
|
+
appPackageJson: packageJson,
|
|
609
|
+
lock,
|
|
610
|
+
packageEntry,
|
|
611
|
+
packageOptions: resolvedOptionsByPackage[packageId],
|
|
612
|
+
io,
|
|
613
|
+
dryRun: options.dryRun === true,
|
|
614
|
+
reason: installReasonByPackage[packageId],
|
|
615
|
+
skipManagedFinalize: options.forceReapplyTarget === true && options.runNpmInstall !== true,
|
|
616
|
+
helpers: installHookHelpers
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
for (const warning of ensureArray(hookResult.warnings)) {
|
|
620
|
+
const normalizedWarning = String(warning || "").trim();
|
|
621
|
+
if (normalizedWarning) {
|
|
622
|
+
prepareHookWarnings.push(normalizedWarning);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
for (const touchedPath of ensureArray(hookResult.touchedFiles)) {
|
|
626
|
+
const normalizedPath = String(touchedPath || "").trim();
|
|
627
|
+
if (normalizedPath) {
|
|
628
|
+
touchedFiles.add(normalizedPath);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (hookResult.stopInstall === true) {
|
|
632
|
+
if (options.dryRun !== true) {
|
|
633
|
+
throw createCliError(`${packageEntry.packageId} lifecycle.install.prepare requested stopInstall outside dry-run.`);
|
|
634
|
+
}
|
|
635
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
636
|
+
const installWarnings = sortStrings([...new Set(prepareHookWarnings)]);
|
|
637
|
+
const stopMessage = String(hookResult.stopMessage || "").trim();
|
|
638
|
+
if (options.json) {
|
|
639
|
+
io.stdout.write(`${JSON.stringify({
|
|
640
|
+
targetType: invocationMode === "generate" ? "generator" : targetType,
|
|
641
|
+
targetId,
|
|
642
|
+
resolvedPackages: resolvedPackageIds,
|
|
643
|
+
touchedFiles: touchedFileList,
|
|
644
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
645
|
+
externalDependencies,
|
|
646
|
+
dryRun: options.dryRun,
|
|
647
|
+
installed: [],
|
|
648
|
+
warnings: installWarnings,
|
|
649
|
+
stoppedAfterPrepare: true,
|
|
650
|
+
message: stopMessage
|
|
651
|
+
}, null, 2)}\n`);
|
|
652
|
+
} else {
|
|
653
|
+
io.stdout.write(
|
|
654
|
+
`${renderResolvedSummary(
|
|
655
|
+
`${invocationMode === "generate" ? "Generated with" : targetType === "bundle" ? "Added bundle" : "Added package"}`,
|
|
656
|
+
targetId,
|
|
657
|
+
resolvedPackageIds,
|
|
658
|
+
touchedFileList,
|
|
659
|
+
appRoot,
|
|
660
|
+
lockPath,
|
|
661
|
+
externalDependencies
|
|
662
|
+
)}\n`
|
|
663
|
+
);
|
|
664
|
+
if (installWarnings.length > 0) {
|
|
665
|
+
io.stdout.write(`Warnings (${installWarnings.length}):\n`);
|
|
666
|
+
for (const warning of installWarnings) {
|
|
667
|
+
io.stdout.write(`- ${warning}\n`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (stopMessage) {
|
|
671
|
+
io.stdout.write(`${stopMessage}\n`);
|
|
672
|
+
}
|
|
673
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
674
|
+
}
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
311
678
|
|
|
312
679
|
for (const packageId of packagesToInstall) {
|
|
313
680
|
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
@@ -319,7 +686,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
319
686
|
lock,
|
|
320
687
|
packageRegistry: combinedPackageRegistry,
|
|
321
688
|
touchedFiles,
|
|
322
|
-
reportTemplateFetchStatus
|
|
689
|
+
reportTemplateFetchStatus,
|
|
690
|
+
dryRun: options.dryRun === true
|
|
323
691
|
});
|
|
324
692
|
installedPackageRecords.push(managedRecord);
|
|
325
693
|
}
|
|
@@ -368,15 +736,60 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
368
736
|
: "Added package";
|
|
369
737
|
const installWarnings = installedPackageRecords
|
|
370
738
|
.flatMap((record) => ensureArray(ensureObject(record).warnings))
|
|
739
|
+
.concat(prepareHookWarnings)
|
|
371
740
|
.map((value) => String(value || "").trim())
|
|
372
741
|
.filter(Boolean);
|
|
742
|
+
const finalizeHookRecords = packagesToInstall
|
|
743
|
+
.map((packageId) => {
|
|
744
|
+
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
745
|
+
const finalizeSpec = ensureObject(ensureObject(ensureObject(packageEntry.descriptor).lifecycle).install).finalize;
|
|
746
|
+
if (Object.keys(ensureObject(finalizeSpec)).length < 1) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
packageEntry,
|
|
751
|
+
hookSpec: finalizeSpec,
|
|
752
|
+
packageOptions: resolvedOptionsByPackage[packageId],
|
|
753
|
+
reason: installReasonByPackage[packageId]
|
|
754
|
+
};
|
|
755
|
+
})
|
|
756
|
+
.filter(Boolean)
|
|
757
|
+
.sort((left, right) => Number(Boolean(right.hookSpec?.managesNpmInstall)) - Number(Boolean(left.hookSpec?.managesNpmInstall)));
|
|
758
|
+
const managesNpmInstall = finalizeHookRecords.some((record) => record.hookSpec?.managesNpmInstall === true);
|
|
373
759
|
|
|
374
760
|
if (!options.dryRun) {
|
|
375
761
|
await writeJsonFile(packageJsonPath, packageJson);
|
|
376
762
|
await writeJsonFile(lockPath, lock);
|
|
377
|
-
if (options.runNpmInstall) {
|
|
763
|
+
if (options.runNpmInstall && !managesNpmInstall) {
|
|
378
764
|
await runNpmInstall(appRoot, io.stderr);
|
|
379
765
|
}
|
|
766
|
+
for (const finalizeRecord of finalizeHookRecords) {
|
|
767
|
+
const hookResult = await invokeInstallHook({
|
|
768
|
+
packageEntry: finalizeRecord.packageEntry,
|
|
769
|
+
appRoot,
|
|
770
|
+
hookSpec: finalizeRecord.hookSpec,
|
|
771
|
+
hookLabel: "lifecycle.install.finalize",
|
|
772
|
+
createCliError,
|
|
773
|
+
hookContext: {
|
|
774
|
+
appRoot,
|
|
775
|
+
appPackageJson: packageJson,
|
|
776
|
+
lock,
|
|
777
|
+
packageEntry: finalizeRecord.packageEntry,
|
|
778
|
+
packageOptions: finalizeRecord.packageOptions,
|
|
779
|
+
io,
|
|
780
|
+
dryRun: false,
|
|
781
|
+
reason: finalizeRecord.reason,
|
|
782
|
+
skipManagedFinalize: options.forceReapplyTarget === true && options.runNpmInstall !== true,
|
|
783
|
+
helpers: installHookHelpers
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
for (const warning of ensureArray(hookResult.warnings)) {
|
|
787
|
+
const normalizedWarning = String(warning || "").trim();
|
|
788
|
+
if (normalizedWarning) {
|
|
789
|
+
installWarnings.push(normalizedWarning);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
380
793
|
}
|
|
381
794
|
|
|
382
795
|
if (options.json) {
|
|
@@ -99,7 +99,8 @@ async function runPackageMigrationsCommand(ctx = {}, { positional, options, cwd,
|
|
|
99
99
|
packageOptions: resolvedOptions,
|
|
100
100
|
appRoot,
|
|
101
101
|
lock,
|
|
102
|
-
touchedFiles
|
|
102
|
+
touchedFiles,
|
|
103
|
+
dryRun: options.dryRun === true
|
|
103
104
|
});
|
|
104
105
|
migratedRecords.push(managedRecord);
|
|
105
106
|
for (const warning of ensureArray(ensureObject(managedRecord).warnings)) {
|
|
@@ -14,6 +14,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
14
14
|
options: {
|
|
15
15
|
dryRun: false,
|
|
16
16
|
runNpmInstall: false,
|
|
17
|
+
devlinks: false,
|
|
17
18
|
full: false,
|
|
18
19
|
expanded: false,
|
|
19
20
|
details: false,
|
|
@@ -39,6 +40,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
39
40
|
const options = {
|
|
40
41
|
dryRun: false,
|
|
41
42
|
runNpmInstall: false,
|
|
43
|
+
devlinks: false,
|
|
42
44
|
full: false,
|
|
43
45
|
expanded: false,
|
|
44
46
|
details: false,
|
|
@@ -69,6 +71,10 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
69
71
|
options.runNpmInstall = true;
|
|
70
72
|
continue;
|
|
71
73
|
}
|
|
74
|
+
if (token === "--devlinks") {
|
|
75
|
+
options.devlinks = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
72
78
|
if (token === "--full") {
|
|
73
79
|
options.full = true;
|
|
74
80
|
continue;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const OPTION_FLAG_LABELS = Object.freeze({
|
|
2
2
|
dryRun: "--dry-run",
|
|
3
3
|
runNpmInstall: "--run-npm-install",
|
|
4
|
+
devlinks: "--devlinks",
|
|
4
5
|
full: "--full",
|
|
5
6
|
expanded: "--expanded",
|
|
6
7
|
details: "--details",
|
|
@@ -167,6 +168,32 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
167
168
|
allowedValueOptionNames: Object.freeze([]),
|
|
168
169
|
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
169
170
|
}),
|
|
171
|
+
mobile: Object.freeze({
|
|
172
|
+
command: "mobile",
|
|
173
|
+
aliases: Object.freeze([]),
|
|
174
|
+
showInOverview: true,
|
|
175
|
+
summary: "Run JSKIT-managed mobile-shell helpers.",
|
|
176
|
+
minimalUse: "jskit mobile add capacitor",
|
|
177
|
+
parameters: Object.freeze([
|
|
178
|
+
Object.freeze({
|
|
179
|
+
name: "<subcommand>",
|
|
180
|
+
description: "add (more mobile helpers will live here as Stage 1 expands)."
|
|
181
|
+
})
|
|
182
|
+
]),
|
|
183
|
+
defaults: Object.freeze([
|
|
184
|
+
"The first supported flow is jskit mobile add capacitor.",
|
|
185
|
+
"Use jskit mobile <subcommand> help for subcommand-specific usage.",
|
|
186
|
+
"--dry-run is accepted by jskit mobile add/sync/run/build.",
|
|
187
|
+
"--devlinks runs npm run --if-present devlinks after install/sync maintenance for development-only relinking."
|
|
188
|
+
]),
|
|
189
|
+
fullUse: "jskit mobile <subcommand> [help] [--dry-run] [--<option> <value>...]",
|
|
190
|
+
showHelpOnBareInvocation: true,
|
|
191
|
+
handlerName: "commandMobile",
|
|
192
|
+
allowedFlagKeys: Object.freeze(["dryRun", "devlinks"]),
|
|
193
|
+
inlineOptionMode: "delegate",
|
|
194
|
+
allowedValueOptionNames: Object.freeze([]),
|
|
195
|
+
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
196
|
+
}),
|
|
170
197
|
add: Object.freeze({
|
|
171
198
|
command: "add",
|
|
172
199
|
aliases: Object.freeze([]),
|
|
@@ -187,13 +214,14 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
187
214
|
"No npm install runs unless --run-npm-install is passed.",
|
|
188
215
|
"Short ids resolve to @jskit-ai/<id> when available.",
|
|
189
216
|
"Running without args lists bundles and runtime packages.",
|
|
190
|
-
"Existing matching version is skipped unless options force reapply."
|
|
217
|
+
"Existing matching version is skipped unless options force reapply.",
|
|
218
|
+
"--devlinks runs npm run --if-present devlinks after install when the app defines that script."
|
|
191
219
|
]),
|
|
192
220
|
fullUse:
|
|
193
|
-
"jskit add <package|bundle> <id> [--<option> <value>...] [--dry-run] [--run-npm-install] [--json] [--verbose]",
|
|
221
|
+
"jskit add <package|bundle> <id> [--<option> <value>...] [--dry-run] [--run-npm-install] [--devlinks] [--json] [--verbose]",
|
|
194
222
|
showHelpOnBareInvocation: false,
|
|
195
223
|
handlerName: "commandAdd",
|
|
196
|
-
allowedFlagKeys: Object.freeze(["dryRun", "runNpmInstall", "json", "verbose"]),
|
|
224
|
+
allowedFlagKeys: Object.freeze(["dryRun", "runNpmInstall", "devlinks", "json", "verbose"]),
|
|
197
225
|
inlineOptionMode: "delegate",
|
|
198
226
|
allowedValueOptionNames: Object.freeze([]),
|
|
199
227
|
canDelegateInlineOptions: canDelegateAddInlineOptions
|
|
@@ -3,6 +3,7 @@ import { createListCommands } from "../commandHandlers/list.js";
|
|
|
3
3
|
import { createShowCommand } from "../commandHandlers/show.js";
|
|
4
4
|
import { createPackageCommands } from "../commandHandlers/package.js";
|
|
5
5
|
import { createAppCommands } from "../commandHandlers/app.js";
|
|
6
|
+
import { createMobileCommands } from "../commandHandlers/mobile.js";
|
|
6
7
|
import { createHealthCommands } from "../commandHandlers/health.js";
|
|
7
8
|
import { createCompletionCommands } from "../commandHandlers/completion.js";
|
|
8
9
|
|
|
@@ -25,6 +26,7 @@ function createCommandHandlers(deps = {}) {
|
|
|
25
26
|
commandRemove
|
|
26
27
|
} = createPackageCommands(commandContext);
|
|
27
28
|
const { commandApp } = createAppCommands(commandContext);
|
|
29
|
+
const { commandMobile } = createMobileCommands(commandContext, { commandAdd });
|
|
28
30
|
const { commandDoctor, commandLintDescriptors } = createHealthCommands(commandContext);
|
|
29
31
|
const { commandCompletion } = createCompletionCommands(commandContext);
|
|
30
32
|
|
|
@@ -35,6 +37,7 @@ function createCommandHandlers(deps = {}) {
|
|
|
35
37
|
commandCompletion,
|
|
36
38
|
commandShow,
|
|
37
39
|
commandApp,
|
|
40
|
+
commandMobile,
|
|
38
41
|
commandCreate,
|
|
39
42
|
commandAdd,
|
|
40
43
|
commandGenerate,
|