@jskit-ai/jskit-cli 0.2.42 → 0.2.43
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 -3
- package/src/server/cliRuntime/completion.js +1177 -0
- package/src/server/cliRuntime/descriptorValidation.js +18 -3
- package/src/server/cliRuntime/ioAndMigrations.js +2 -2
- package/src/server/cliRuntime/mutationApplication.js +1 -1
- package/src/server/cliRuntime/mutationWhen.js +2 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +188 -143
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +11 -38
- package/src/server/cliRuntime/mutations/templateContext.js +8 -14
- package/src/server/cliRuntime/packageInstallFlow.js +36 -21
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +13 -22
- package/src/server/cliRuntime/packageOptions.js +129 -3
- package/src/server/cliRuntime/packageRegistries.js +3 -2
- package/src/server/commandHandlers/completion.js +129 -0
- package/src/server/commandHandlers/list.js +4 -6
- package/src/server/commandHandlers/packageCommands/add.js +31 -11
- package/src/server/commandHandlers/packageCommands/discoverabilityHelp.js +10 -2
- package/src/server/commandHandlers/packageCommands/generate.js +28 -1
- package/src/server/commandHandlers/packageCommands/tabLinkItemProvisioning.js +123 -164
- package/src/server/commandHandlers/shared.js +23 -3
- package/src/server/commandHandlers/show/renderPackageText.js +3 -3
- package/src/server/core/argParser.js +9 -0
- package/src/server/core/commandCatalog.js +36 -13
- package/src/server/core/createCommandHandlers.js +3 -0
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from "./discoverabilityHelp.js";
|
|
8
8
|
import { interpolateOptionValue } from "../../shared/optionInterpolation.js";
|
|
9
9
|
|
|
10
|
+
const SHELL_WEB_PACKAGE_ID = "@jskit-ai/shell-web";
|
|
11
|
+
|
|
10
12
|
function resolveGeneratorSubcommandDefinitionMetadata(packageEntry = {}, subcommandName = "") {
|
|
11
13
|
const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
|
|
12
14
|
? packageEntry.descriptor
|
|
@@ -27,6 +29,10 @@ function resolveGeneratorSubcommandDefinitionMetadata(packageEntry = {}, subcomm
|
|
|
27
29
|
return definition && typeof definition === "object" ? definition : {};
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
function resolveSubcommandRequiresShellWeb(packageEntry = {}, subcommandName = "") {
|
|
33
|
+
return resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName)?.requiresShellWeb === true;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function mapDescriptorBackedSubcommandArgsToInlineOptions(
|
|
31
37
|
packageEntry = {},
|
|
32
38
|
subcommandName = "",
|
|
@@ -199,6 +205,7 @@ async function runPackageGenerateCommand(
|
|
|
199
205
|
resolvePackageKind,
|
|
200
206
|
resolveGeneratorPrimarySubcommand,
|
|
201
207
|
hasGeneratorSubcommandDefinition,
|
|
208
|
+
loadLockFile,
|
|
202
209
|
readdir,
|
|
203
210
|
validateInlineOptionValuesForPackage,
|
|
204
211
|
runGeneratorSubcommand,
|
|
@@ -360,6 +367,15 @@ async function runPackageGenerateCommand(
|
|
|
360
367
|
appRoot,
|
|
361
368
|
optionNames: validatedOptionNames
|
|
362
369
|
});
|
|
370
|
+
if (resolveSubcommandRequiresShellWeb(packageEntry, normalizedSubcommandName)) {
|
|
371
|
+
const { lock } = await loadLockFile(appRoot);
|
|
372
|
+
if (!lock?.installedPackages?.[SHELL_WEB_PACKAGE_ID]) {
|
|
373
|
+
const commandLabel = `${String(targetId || resolvedPackageId || "").trim() || resolvedPackageId} ${normalizedSubcommandName}`.trim();
|
|
374
|
+
throw createCliError(
|
|
375
|
+
`Generator command ${commandLabel} requires ${SHELL_WEB_PACKAGE_ID} to be installed in this app. Run: npx jskit add package shell-web`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
363
379
|
|
|
364
380
|
const primarySubcommand = resolveGeneratorPrimarySubcommand(packageEntry);
|
|
365
381
|
if (
|
|
@@ -432,7 +448,18 @@ async function runPackageGenerateCommand(
|
|
|
432
448
|
});
|
|
433
449
|
}
|
|
434
450
|
|
|
435
|
-
const
|
|
451
|
+
const resolvedGeneratorPackage = await resolveGeneratorPackageEntry(targetId);
|
|
452
|
+
const { packageEntry } = resolvedGeneratorPackage;
|
|
453
|
+
const primarySubcommand = resolveGeneratorPrimarySubcommand(packageEntry);
|
|
454
|
+
const hasInlineOptions = Object.keys(options?.inlineOptions || {}).length > 0;
|
|
455
|
+
if (primarySubcommand && hasInlineOptions) {
|
|
456
|
+
return runResolvedGeneratorSubcommand({
|
|
457
|
+
...resolvedGeneratorPackage,
|
|
458
|
+
subcommandName: primarySubcommand,
|
|
459
|
+
subcommandArgs: []
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
436
463
|
renderGeneratePackageHelp({
|
|
437
464
|
io,
|
|
438
465
|
packageEntry,
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
LOCAL_LINK_ITEM_COMPONENT_DEFINITIONS,
|
|
5
|
+
findLocalLinkItemDefinition,
|
|
6
|
+
readLocalLinkItemComponentSource
|
|
7
|
+
} from "@jskit-ai/shell-web/server/support/localLinkItemScaffolds";
|
|
3
8
|
|
|
4
9
|
const MAIN_CLIENT_PROVIDER_FILE = "packages/main/src/client/providers/MainClientProvider.js";
|
|
5
|
-
const
|
|
6
|
-
const
|
|
10
|
+
const PLACEMENT_FILE = "src/placement.js";
|
|
11
|
+
const PLACEMENT_COMPONENT_TOKEN_PATTERN = /\bcomponentToken\s*:\s*["']([^"']+)["']/g;
|
|
7
12
|
const TAB_LINK_COMPONENT_TOKEN = "local.main.ui.tab-link-item";
|
|
8
13
|
|
|
14
|
+
const LOCAL_LINK_ITEM_COMPONENT_TOKENS = Object.freeze(
|
|
15
|
+
LOCAL_LINK_ITEM_COMPONENT_DEFINITIONS.map((entry) => entry.token)
|
|
16
|
+
);
|
|
17
|
+
|
|
9
18
|
function toPosixPath(value = "") {
|
|
10
19
|
return String(value || "").replaceAll("\\", "/");
|
|
11
20
|
}
|
|
@@ -58,7 +67,7 @@ function insertBeforeClassDeclaration(source = "", line = "", { className = "",
|
|
|
58
67
|
const classPattern = new RegExp(`^class\\s+${String(className || "").trim()}\\b`, "m");
|
|
59
68
|
const classMatch = classPattern.exec(sourceText);
|
|
60
69
|
if (!classMatch) {
|
|
61
|
-
throw new Error(`
|
|
70
|
+
throw new Error(`placement component provisioning could not find ${className} class declaration in ${contextFile || "target file"}.`);
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
return {
|
|
@@ -67,143 +76,42 @@ function insertBeforeClassDeclaration(source = "", line = "", { className = "",
|
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const props = defineProps({
|
|
78
|
-
label: {
|
|
79
|
-
type: String,
|
|
80
|
-
default: ""
|
|
81
|
-
},
|
|
82
|
-
to: {
|
|
83
|
-
type: String,
|
|
84
|
-
default: ""
|
|
85
|
-
},
|
|
86
|
-
surface: {
|
|
87
|
-
type: String,
|
|
88
|
-
default: ""
|
|
89
|
-
},
|
|
90
|
-
workspaceSuffix: {
|
|
91
|
-
type: String,
|
|
92
|
-
default: "/"
|
|
93
|
-
},
|
|
94
|
-
nonWorkspaceSuffix: {
|
|
95
|
-
type: String,
|
|
96
|
-
default: "/"
|
|
97
|
-
},
|
|
98
|
-
disabled: {
|
|
99
|
-
type: Boolean,
|
|
100
|
-
default: false
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const route = useRoute();
|
|
105
|
-
const paths = usePaths();
|
|
106
|
-
const { currentSurfaceId, workspaceSlugFromRoute } = useWorkspaceRouteContext();
|
|
107
|
-
|
|
108
|
-
function normalizePathname(pathname = "") {
|
|
109
|
-
const source = String(pathname || "").trim();
|
|
110
|
-
if (!source) {
|
|
111
|
-
return "";
|
|
79
|
+
async function collectProvisionableLocalPlacementComponentTokensFromApp({
|
|
80
|
+
appRoot = ""
|
|
81
|
+
} = {}) {
|
|
82
|
+
const placementAbsolutePath = path.join(appRoot, PLACEMENT_FILE);
|
|
83
|
+
const placementSource = await readUtf8FileIfExists(placementAbsolutePath);
|
|
84
|
+
if (!placementSource) {
|
|
85
|
+
return [];
|
|
112
86
|
}
|
|
113
87
|
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
: Math.min(queryIndex, hashIndex);
|
|
122
|
-
return cutoff < 0 ? source : source.slice(0, cutoff);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function interpolateBracketParams(pathTemplate = "", params = {}) {
|
|
126
|
-
const source = String(pathTemplate || "").trim();
|
|
127
|
-
if (!source) {
|
|
128
|
-
return "";
|
|
88
|
+
const collectedTokens = new Set();
|
|
89
|
+
for (const match of String(placementSource).matchAll(PLACEMENT_COMPONENT_TOKEN_PATTERN)) {
|
|
90
|
+
const componentToken = String(match[1] || "").trim();
|
|
91
|
+
if (!findLocalLinkItemDefinition(componentToken)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
collectedTokens.add(componentToken);
|
|
129
95
|
}
|
|
130
96
|
|
|
131
|
-
return
|
|
132
|
-
const key = String(rawKey || "").trim();
|
|
133
|
-
if (!key) {
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
const value = params?.[key];
|
|
137
|
-
return value == null ? "[" + key + "]" : encodeURIComponent(String(value));
|
|
138
|
-
});
|
|
97
|
+
return Array.from(collectedTokens).sort((left, right) => left.localeCompare(right));
|
|
139
98
|
}
|
|
140
99
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const workspaceSlug = String(workspaceSlugFromRoute.value || "").trim();
|
|
154
|
-
const suffixTemplate = workspaceSlug ? props.workspaceSuffix : props.nonWorkspaceSuffix;
|
|
155
|
-
const interpolatedSuffix = interpolateBracketParams(suffixTemplate, route.params || {});
|
|
156
|
-
if (interpolatedSuffix && !interpolatedSuffix.includes("[")) {
|
|
157
|
-
return paths.page(interpolatedSuffix, {
|
|
158
|
-
surface: targetSurfaceId.value,
|
|
159
|
-
mode: "auto"
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return explicitTo;
|
|
100
|
+
async function resolveProvisionableLocalPlacementComponentTokens({
|
|
101
|
+
appRoot = "",
|
|
102
|
+
componentTokens = []
|
|
103
|
+
} = {}) {
|
|
104
|
+
const collectedTokens = new Set(
|
|
105
|
+
(Array.isArray(componentTokens) ? componentTokens : [])
|
|
106
|
+
.map((value) => String(value || "").trim())
|
|
107
|
+
.filter((value) => Boolean(findLocalLinkItemDefinition(value)))
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
for (const componentToken of await collectProvisionableLocalPlacementComponentTokensFromApp({ appRoot })) {
|
|
111
|
+
collectedTokens.add(componentToken);
|
|
164
112
|
}
|
|
165
113
|
|
|
166
|
-
|
|
167
|
-
const suffix = workspaceSlug ? props.workspaceSuffix : props.nonWorkspaceSuffix;
|
|
168
|
-
const normalizedSuffix = String(suffix || "/").trim() || "/";
|
|
169
|
-
return paths.page(normalizedSuffix, {
|
|
170
|
-
surface: targetSurfaceId.value,
|
|
171
|
-
mode: "auto"
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const isActive = computed(() => {
|
|
176
|
-
const targetPath = normalizePathname(resolvedTo.value);
|
|
177
|
-
const currentPath = normalizePathname(route.fullPath || route.path || "");
|
|
178
|
-
if (!targetPath || !currentPath) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
return currentPath === targetPath || currentPath.startsWith(\`\${targetPath}/\`);
|
|
182
|
-
});
|
|
183
|
-
</script>
|
|
184
|
-
|
|
185
|
-
<template>
|
|
186
|
-
<v-btn
|
|
187
|
-
class="tab-link-item"
|
|
188
|
-
variant="text"
|
|
189
|
-
size="small"
|
|
190
|
-
:to="resolvedTo"
|
|
191
|
-
:active="isActive"
|
|
192
|
-
:disabled="disabled"
|
|
193
|
-
color="primary"
|
|
194
|
-
>
|
|
195
|
-
{{ label || "Tab" }}
|
|
196
|
-
</v-btn>
|
|
197
|
-
</template>
|
|
198
|
-
|
|
199
|
-
<style scoped>
|
|
200
|
-
.tab-link-item {
|
|
201
|
-
text-transform: none;
|
|
202
|
-
font-weight: 600;
|
|
203
|
-
border-radius: 999px;
|
|
204
|
-
}
|
|
205
|
-
</style>
|
|
206
|
-
`;
|
|
114
|
+
return Array.from(collectedTokens).sort((left, right) => left.localeCompare(right));
|
|
207
115
|
}
|
|
208
116
|
|
|
209
117
|
async function readUtf8FileIfExists(absolutePath = "") {
|
|
@@ -217,28 +125,35 @@ async function readUtf8FileIfExists(absolutePath = "") {
|
|
|
217
125
|
}
|
|
218
126
|
}
|
|
219
127
|
|
|
220
|
-
async function
|
|
221
|
-
|
|
128
|
+
async function ensureProvisionedComponentFile(
|
|
129
|
+
definition,
|
|
130
|
+
{
|
|
131
|
+
appRoot = "",
|
|
132
|
+
dryRun = false,
|
|
133
|
+
touchedFiles = new Set()
|
|
134
|
+
} = {}
|
|
135
|
+
) {
|
|
136
|
+
const componentRelativePath = definition.componentFile;
|
|
222
137
|
const componentAbsolutePath = path.join(appRoot, componentRelativePath);
|
|
223
138
|
const existingComponentSource = await readUtf8FileIfExists(componentAbsolutePath);
|
|
224
139
|
if (existingComponentSource) {
|
|
225
140
|
return;
|
|
226
141
|
}
|
|
227
142
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
143
|
+
if (dryRun !== true) {
|
|
144
|
+
await mkdir(path.dirname(componentAbsolutePath), { recursive: true });
|
|
145
|
+
await writeFile(componentAbsolutePath, await readLocalLinkItemComponentSource(definition), "utf8");
|
|
146
|
+
}
|
|
232
147
|
touchedFiles.add(toPosixPath(componentRelativePath));
|
|
233
148
|
}
|
|
234
149
|
|
|
235
|
-
function
|
|
236
|
-
const tokenPattern =
|
|
150
|
+
function hasProvisionedTokenRegistration(providerSource = "", componentToken = "") {
|
|
151
|
+
const tokenPattern = String(componentToken || "").replaceAll(".", "\\.");
|
|
237
152
|
const pattern = new RegExp(`registerMainClientComponent\\(\\s*"${tokenPattern}"\\s*,`, "m");
|
|
238
153
|
return pattern.test(String(providerSource || ""));
|
|
239
154
|
}
|
|
240
155
|
|
|
241
|
-
async function loadMainClientProviderSource({ appRoot = "", createCliError } = {}) {
|
|
156
|
+
async function loadMainClientProviderSource({ appRoot = "", createCliError, componentToken = "" } = {}) {
|
|
242
157
|
const providerAbsolutePath = path.join(appRoot, MAIN_CLIENT_PROVIDER_FILE);
|
|
243
158
|
let providerSource = "";
|
|
244
159
|
try {
|
|
@@ -246,7 +161,7 @@ async function loadMainClientProviderSource({ appRoot = "", createCliError } = {
|
|
|
246
161
|
} catch (error) {
|
|
247
162
|
if (error && error.code === "ENOENT") {
|
|
248
163
|
throw createCliError(
|
|
249
|
-
`
|
|
164
|
+
`placement component token "${componentToken}" requires ${MAIN_CLIENT_PROVIDER_FILE}.`
|
|
250
165
|
);
|
|
251
166
|
}
|
|
252
167
|
throw error;
|
|
@@ -254,31 +169,35 @@ async function loadMainClientProviderSource({ appRoot = "", createCliError } = {
|
|
|
254
169
|
|
|
255
170
|
if (!/\bregisterMainClientComponent\s*\(/.test(providerSource)) {
|
|
256
171
|
throw createCliError(
|
|
257
|
-
`
|
|
172
|
+
`placement component token "${componentToken}" could not find registerMainClientComponent() contract in ${MAIN_CLIENT_PROVIDER_FILE}.`
|
|
258
173
|
);
|
|
259
174
|
}
|
|
260
175
|
|
|
261
176
|
return providerSource;
|
|
262
177
|
}
|
|
263
178
|
|
|
264
|
-
async function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
179
|
+
async function ensureProvisionedProviderRegistration(
|
|
180
|
+
definition,
|
|
181
|
+
{
|
|
182
|
+
appRoot = "",
|
|
183
|
+
createCliError,
|
|
184
|
+
dryRun = false,
|
|
185
|
+
touchedFiles = new Set()
|
|
186
|
+
} = {}
|
|
187
|
+
) {
|
|
270
188
|
const providerRelativePath = MAIN_CLIENT_PROVIDER_FILE;
|
|
271
189
|
const providerAbsolutePath = path.join(appRoot, providerRelativePath);
|
|
272
190
|
const providerSource = await loadMainClientProviderSource({
|
|
273
191
|
appRoot,
|
|
274
|
-
createCliError
|
|
192
|
+
createCliError,
|
|
193
|
+
componentToken: definition.token
|
|
275
194
|
});
|
|
276
|
-
if (
|
|
195
|
+
if (hasProvisionedTokenRegistration(providerSource, definition.token)) {
|
|
277
196
|
return false;
|
|
278
197
|
}
|
|
279
198
|
|
|
280
|
-
const importLine = `import ${
|
|
281
|
-
const registerLine = `registerMainClientComponent("${
|
|
199
|
+
const importLine = `import ${definition.componentName} from "/${toPosixPath(definition.componentFile)}";`;
|
|
200
|
+
const registerLine = `registerMainClientComponent("${definition.token}", () => ${definition.componentName});`;
|
|
282
201
|
|
|
283
202
|
const importApplied = insertImportIfMissing(providerSource, importLine);
|
|
284
203
|
const registerApplied = insertBeforeClassDeclaration(importApplied.content, registerLine, {
|
|
@@ -297,30 +216,70 @@ async function ensureTabLinkItemProviderRegistration({
|
|
|
297
216
|
return true;
|
|
298
217
|
}
|
|
299
218
|
|
|
300
|
-
async function
|
|
219
|
+
async function ensureLocalMainPlacementComponentProvisioning({
|
|
301
220
|
appRoot = "",
|
|
302
221
|
createCliError,
|
|
303
222
|
dryRun = false,
|
|
304
|
-
touchedFiles = new Set()
|
|
223
|
+
touchedFiles = new Set(),
|
|
224
|
+
componentTokens = []
|
|
305
225
|
} = {}) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
226
|
+
const uniqueComponentTokens = Array.from(
|
|
227
|
+
new Set(
|
|
228
|
+
(Array.isArray(componentTokens) ? componentTokens : [])
|
|
229
|
+
.map((value) => String(value || "").trim())
|
|
230
|
+
.filter(Boolean)
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
for (const componentToken of uniqueComponentTokens) {
|
|
235
|
+
const definition = findLocalLinkItemDefinition(componentToken);
|
|
236
|
+
if (!definition) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const providerSource = await loadMainClientProviderSource({
|
|
241
|
+
appRoot,
|
|
242
|
+
createCliError,
|
|
243
|
+
componentToken: definition.token
|
|
244
|
+
});
|
|
245
|
+
if (hasProvisionedTokenRegistration(providerSource, definition.token)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await ensureProvisionedComponentFile(definition, {
|
|
250
|
+
appRoot,
|
|
251
|
+
dryRun,
|
|
252
|
+
touchedFiles
|
|
253
|
+
});
|
|
254
|
+
await ensureProvisionedProviderRegistration(definition, {
|
|
255
|
+
appRoot,
|
|
256
|
+
createCliError,
|
|
257
|
+
dryRun,
|
|
258
|
+
touchedFiles
|
|
259
|
+
});
|
|
312
260
|
}
|
|
261
|
+
}
|
|
313
262
|
|
|
314
|
-
|
|
315
|
-
|
|
263
|
+
async function ensureLocalMainTabLinkItemProvisioning({
|
|
264
|
+
appRoot = "",
|
|
265
|
+
createCliError,
|
|
266
|
+
dryRun = false,
|
|
267
|
+
touchedFiles = new Set()
|
|
268
|
+
} = {}) {
|
|
269
|
+
return ensureLocalMainPlacementComponentProvisioning({
|
|
316
270
|
appRoot,
|
|
317
271
|
createCliError,
|
|
318
272
|
dryRun,
|
|
319
|
-
touchedFiles
|
|
273
|
+
touchedFiles,
|
|
274
|
+
componentTokens: [TAB_LINK_COMPONENT_TOKEN]
|
|
320
275
|
});
|
|
321
276
|
}
|
|
322
277
|
|
|
323
278
|
export {
|
|
279
|
+
LOCAL_LINK_ITEM_COMPONENT_TOKENS,
|
|
324
280
|
TAB_LINK_COMPONENT_TOKEN,
|
|
281
|
+
collectProvisionableLocalPlacementComponentTokensFromApp,
|
|
282
|
+
resolveProvisionableLocalPlacementComponentTokens,
|
|
283
|
+
ensureLocalMainPlacementComponentProvisioning,
|
|
325
284
|
ensureLocalMainTabLinkItemProvisioning
|
|
326
285
|
};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { importFreshModuleFromAbsolutePath } from "@jskit-ai/kernel/server/support";
|
|
3
3
|
import {
|
|
4
4
|
ensureArray,
|
|
5
5
|
ensureObject,
|
|
6
6
|
sortStrings
|
|
7
7
|
} from "../shared/collectionUtils.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureLocalMainPlacementComponentProvisioning,
|
|
10
|
+
resolveProvisionableLocalPlacementComponentTokens
|
|
11
|
+
} from "./packageCommands/tabLinkItemProvisioning.js";
|
|
8
12
|
|
|
9
13
|
function createCommandHandlerShared(ctx = {}) {
|
|
10
14
|
const {
|
|
@@ -242,7 +246,7 @@ function createCommandHandlerShared(ctx = {}) {
|
|
|
242
246
|
|
|
243
247
|
let moduleNamespace = null;
|
|
244
248
|
try {
|
|
245
|
-
moduleNamespace = await
|
|
249
|
+
moduleNamespace = await importFreshModuleFromAbsolutePath(entrypointPath);
|
|
246
250
|
} catch (error) {
|
|
247
251
|
throw createCliError(
|
|
248
252
|
`Unable to load generator subcommand entrypoint ${normalizeRelativePath(appRoot, entrypointPath)}: ${String(error?.message || error || "unknown error")}`
|
|
@@ -268,9 +272,25 @@ function createCommandHandlerShared(ctx = {}) {
|
|
|
268
272
|
dryRun: dryRun === true
|
|
269
273
|
});
|
|
270
274
|
const payload = ensureObject(result);
|
|
271
|
-
const
|
|
275
|
+
const touchedFileSet = new Set(
|
|
272
276
|
ensureArray(payload.touchedFiles).map((value) => String(value || "").trim()).filter(Boolean)
|
|
273
277
|
);
|
|
278
|
+
const placementComponentTokens = await resolveProvisionableLocalPlacementComponentTokens({
|
|
279
|
+
appRoot,
|
|
280
|
+
componentTokens: ensureArray(payload.placementComponentTokens)
|
|
281
|
+
.map((value) => String(value || "").trim())
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
});
|
|
284
|
+
if (placementComponentTokens.length > 0) {
|
|
285
|
+
await ensureLocalMainPlacementComponentProvisioning({
|
|
286
|
+
appRoot,
|
|
287
|
+
createCliError,
|
|
288
|
+
dryRun: dryRun === true,
|
|
289
|
+
touchedFiles: touchedFileSet,
|
|
290
|
+
componentTokens: placementComponentTokens
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const touchedFiles = sortStrings([...touchedFileSet]);
|
|
274
294
|
const summary = String(payload.summary || "").trim();
|
|
275
295
|
|
|
276
296
|
if (json) {
|
|
@@ -99,13 +99,13 @@ function renderPackagePayloadText({
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (placementOutlets.length > 0) {
|
|
102
|
-
stdout.write(`${color.heading(`Placement outlets (
|
|
102
|
+
stdout.write(`${color.heading(`Placement outlets (${placementOutlets.length}):`)}\n`);
|
|
103
103
|
for (const outlet of placementOutlets) {
|
|
104
104
|
const surfaces = ensureArray(outlet.surfaces).map((value) => String(value || "").trim()).filter(Boolean);
|
|
105
105
|
const surfacesLabel = surfaces.length > 0 ? ` ${color.installed(`[surfaces:${surfaces.join(", ")}]`)}` : "";
|
|
106
106
|
const description = String(outlet.description || "").trim();
|
|
107
107
|
const descriptionSuffix = description ? `: ${description}` : "";
|
|
108
|
-
stdout.write(`- ${color.item(
|
|
108
|
+
stdout.write(`- ${color.item(outlet.target)}${surfacesLabel}${descriptionSuffix}\n`);
|
|
109
109
|
if (options.details) {
|
|
110
110
|
const sourceLabel = String(outlet.source || "").trim();
|
|
111
111
|
if (sourceLabel) {
|
|
@@ -126,7 +126,7 @@ function renderPackagePayloadText({
|
|
|
126
126
|
const description = String(contribution.description || "").trim();
|
|
127
127
|
const descriptionSuffix = description ? `: ${description}` : "";
|
|
128
128
|
stdout.write(
|
|
129
|
-
`- ${color.item(contribution.id)} ${color.dim("->")} ${color.item(
|
|
129
|
+
`- ${color.item(contribution.id)} ${color.dim("->")} ${color.item(contribution.target)} ${color.installed(`[surfaces:${surfacesLabel}]`)}${orderSuffix}${componentSuffix}${descriptionSuffix}\n`
|
|
130
130
|
);
|
|
131
131
|
if (options.details) {
|
|
132
132
|
const when = String(contribution.when || "").trim();
|
|
@@ -56,6 +56,11 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
56
56
|
while (args.length > 0) {
|
|
57
57
|
const token = String(args.shift() || "");
|
|
58
58
|
|
|
59
|
+
if (token === "--") {
|
|
60
|
+
positional.push(...args.map((value) => String(value || "")));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
if (token === "--dry-run") {
|
|
60
65
|
options.dryRun = true;
|
|
61
66
|
continue;
|
|
@@ -104,6 +109,10 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
104
109
|
options.inlineOptions.force = "true";
|
|
105
110
|
continue;
|
|
106
111
|
}
|
|
112
|
+
if (token === "--install") {
|
|
113
|
+
options.inlineOptions.install = "true";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
107
116
|
|
|
108
117
|
if (token.startsWith("--")) {
|
|
109
118
|
const withoutPrefix = token.slice(2);
|
|
@@ -30,9 +30,8 @@ function canDelegateAddInlineOptions(positional = []) {
|
|
|
30
30
|
function canDelegateGenerateInlineOptions(positional = []) {
|
|
31
31
|
const normalizedPositionals = Array.isArray(positional) ? positional : [];
|
|
32
32
|
const first = String(normalizedPositionals[0] || "").trim();
|
|
33
|
-
const second = String(normalizedPositionals[1] || "").trim();
|
|
34
33
|
const last = String(normalizedPositionals[normalizedPositionals.length - 1] || "").trim();
|
|
35
|
-
if (!first ||
|
|
34
|
+
if (!first || isHelpToken(first) || isHelpToken(last)) {
|
|
36
35
|
return false;
|
|
37
36
|
}
|
|
38
37
|
return true;
|
|
@@ -77,6 +76,31 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
77
76
|
inlineOptionMode: "none",
|
|
78
77
|
allowedValueOptionNames: Object.freeze([])
|
|
79
78
|
}),
|
|
79
|
+
completion: Object.freeze({
|
|
80
|
+
command: "completion",
|
|
81
|
+
aliases: Object.freeze([]),
|
|
82
|
+
showInOverview: true,
|
|
83
|
+
summary: "Print shell completion script support.",
|
|
84
|
+
minimalUse: "jskit completion bash",
|
|
85
|
+
parameters: Object.freeze([
|
|
86
|
+
Object.freeze({
|
|
87
|
+
name: "<shell>",
|
|
88
|
+
description: "Shell name. Currently only bash is supported."
|
|
89
|
+
})
|
|
90
|
+
]),
|
|
91
|
+
defaults: Object.freeze([
|
|
92
|
+
"Prints a shell completion script to stdout.",
|
|
93
|
+
"Use --install to write a small Bash loader file and wire ~/.bashrc automatically.",
|
|
94
|
+
"Use source <(npx jskit completion bash) to enable completion in the current shell.",
|
|
95
|
+
"The internal __complete__ mode is reserved for the generated shell function."
|
|
96
|
+
]),
|
|
97
|
+
fullUse: "jskit completion bash [--install]",
|
|
98
|
+
showHelpOnBareInvocation: true,
|
|
99
|
+
handlerName: "commandCompletion",
|
|
100
|
+
allowedFlagKeys: Object.freeze([]),
|
|
101
|
+
inlineOptionMode: "enumerated",
|
|
102
|
+
allowedValueOptionNames: Object.freeze(["install"])
|
|
103
|
+
}),
|
|
80
104
|
create: Object.freeze({
|
|
81
105
|
command: "create",
|
|
82
106
|
aliases: Object.freeze([]),
|
|
@@ -135,7 +159,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
135
159
|
}),
|
|
136
160
|
generate: Object.freeze({
|
|
137
161
|
command: "generate",
|
|
138
|
-
aliases: Object.freeze([
|
|
162
|
+
aliases: Object.freeze([]),
|
|
139
163
|
showInOverview: true,
|
|
140
164
|
summary: "Run a generator package (or generator subcommand).",
|
|
141
165
|
minimalUse: "jskit generate <generatorId>",
|
|
@@ -188,7 +212,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
188
212
|
}),
|
|
189
213
|
list: Object.freeze({
|
|
190
214
|
command: "list",
|
|
191
|
-
aliases: Object.freeze([
|
|
215
|
+
aliases: Object.freeze([]),
|
|
192
216
|
showInOverview: true,
|
|
193
217
|
summary: "List bundles, runtime packages, or generator packages.",
|
|
194
218
|
minimalUse: "jskit list",
|
|
@@ -212,7 +236,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
212
236
|
}),
|
|
213
237
|
"list-placements": Object.freeze({
|
|
214
238
|
command: "list-placements",
|
|
215
|
-
aliases: Object.freeze([
|
|
239
|
+
aliases: Object.freeze([]),
|
|
216
240
|
showInOverview: true,
|
|
217
241
|
summary: "List discovered UI placement targets.",
|
|
218
242
|
minimalUse: "jskit list-placements",
|
|
@@ -229,12 +253,12 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
229
253
|
inlineOptionMode: "none",
|
|
230
254
|
allowedValueOptionNames: Object.freeze([])
|
|
231
255
|
}),
|
|
232
|
-
"list-
|
|
233
|
-
command: "list-
|
|
234
|
-
aliases: Object.freeze([
|
|
256
|
+
"list-component-tokens": Object.freeze({
|
|
257
|
+
command: "list-component-tokens",
|
|
258
|
+
aliases: Object.freeze([]),
|
|
235
259
|
showInOverview: true,
|
|
236
|
-
summary: "List available placement
|
|
237
|
-
minimalUse: "jskit list-
|
|
260
|
+
summary: "List available placement component tokens.",
|
|
261
|
+
minimalUse: "jskit list-component-tokens",
|
|
238
262
|
parameters: Object.freeze([
|
|
239
263
|
Object.freeze({
|
|
240
264
|
name: "[--prefix <value>]",
|
|
@@ -252,7 +276,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
252
276
|
"Use --all when you want the full discovered token set.",
|
|
253
277
|
"Shows plain text by default; use --json for structured output."
|
|
254
278
|
]),
|
|
255
|
-
fullUse: "jskit list-
|
|
279
|
+
fullUse: "jskit list-component-tokens [--prefix <value>] [--all] [--json]",
|
|
256
280
|
showHelpOnBareInvocation: false,
|
|
257
281
|
handlerName: "commandListLinkItems",
|
|
258
282
|
allowedFlagKeys: Object.freeze(["json", "all"]),
|
|
@@ -261,7 +285,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
261
285
|
}),
|
|
262
286
|
show: Object.freeze({
|
|
263
287
|
command: "show",
|
|
264
|
-
aliases: Object.freeze([
|
|
288
|
+
aliases: Object.freeze([]),
|
|
265
289
|
showInOverview: true,
|
|
266
290
|
summary: "Show detailed metadata for a bundle or package.",
|
|
267
291
|
minimalUse: "jskit show <id>",
|
|
@@ -272,7 +296,6 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
272
296
|
})
|
|
273
297
|
]),
|
|
274
298
|
defaults: Object.freeze([
|
|
275
|
-
"view is an alias of show.",
|
|
276
299
|
"Basic output is compact; --details expands capability and runtime sections.",
|
|
277
300
|
"--debug-exports implies --details."
|
|
278
301
|
]),
|