@jskit-ai/jskit-cli 0.2.41 → 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/mutations/textMutations.js +11 -6
- package/src/server/cliRuntime/packageInstallFlow.js +36 -21
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +13 -22
- package/src/server/cliRuntime/packageOptions.js +149 -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 +29 -31
- 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 +12 -2
- package/src/server/core/commandCatalog.js +36 -13
- package/src/server/core/createCommandHandlers.js +3 -0
- package/src/server/shared/optionInterpolation.js +93 -0
|
@@ -54,10 +54,15 @@ function buildPackageOptionRows(packageEntry = {}) {
|
|
|
54
54
|
|
|
55
55
|
for (const optionName of sortStrings(Object.keys(optionSchemas))) {
|
|
56
56
|
const schema = ensureObject(optionSchemas[optionName]);
|
|
57
|
+
const allowedValues = ensureArray(schema.allowedValues)
|
|
58
|
+
.map((value) => String(typeof value === "string" ? value : ensureObject(value).value || "").trim())
|
|
59
|
+
.filter(Boolean);
|
|
57
60
|
rows.push(Object.freeze({
|
|
58
61
|
name: optionName,
|
|
59
62
|
required: schema.required === true,
|
|
60
63
|
inputType: String(schema.inputType || "text").trim() || "text",
|
|
64
|
+
validationType: String(schema.validationType || "").trim(),
|
|
65
|
+
allowedValues,
|
|
61
66
|
defaultValue: String(schema.defaultValue || "").trim(),
|
|
62
67
|
allowEmpty: schema.allowEmpty === true,
|
|
63
68
|
promptLabel: String(schema.promptLabel || "").trim(),
|
|
@@ -326,9 +331,12 @@ function formatOptionSummary(optionRow = {}, { color = null } = {}) {
|
|
|
326
331
|
? `; default: ${optionRow.defaultValue}`
|
|
327
332
|
: optionalDefaultSuffix;
|
|
328
333
|
const allowEmptySuffix = optionRow.allowEmpty ? "; allow-empty" : "";
|
|
334
|
+
const allowedValuesHint = Array.isArray(optionRow.allowedValues) && optionRow.allowedValues.length > 0
|
|
335
|
+
? `Allowed values: ${optionRow.allowedValues.join(", ")}.`
|
|
336
|
+
: "";
|
|
329
337
|
const labelParts = [
|
|
330
338
|
String(optionRow.helpLabel || "").trim() || String(optionRow.promptLabel || "").trim(),
|
|
331
|
-
String(optionRow.helpHint || "").trim() || String(optionRow.promptHint || "").trim()
|
|
339
|
+
String(optionRow.helpHint || "").trim() || String(optionRow.promptHint || "").trim() || allowedValuesHint
|
|
332
340
|
].filter(Boolean);
|
|
333
341
|
const label = labelParts.join(". ");
|
|
334
342
|
const normalizedInputType = String(optionRow.inputType || "").trim().toLowerCase();
|
|
@@ -337,7 +345,7 @@ function formatOptionSummary(optionRow = {}, { color = null } = {}) {
|
|
|
337
345
|
: "";
|
|
338
346
|
const baseDescription = label || "No description provided.";
|
|
339
347
|
const description = optionRow.name === "placement-component-token"
|
|
340
|
-
? `${baseDescription} Use \`jskit list-
|
|
348
|
+
? `${baseDescription} Use \`jskit list-component-tokens\` to discover link-item tokens.`
|
|
341
349
|
: baseDescription;
|
|
342
350
|
const optionName = `--${optionRow.name}`;
|
|
343
351
|
const renderedOptionName = color ? color.item(optionName) : optionName;
|
|
@@ -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 = "",
|
|
@@ -73,40 +79,11 @@ function mapDescriptorBackedSubcommandArgsToInlineOptions(
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
function resolveSubcommandRequiresInput(packageEntry = {}, subcommandName = "") {
|
|
76
|
-
const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
|
|
77
|
-
? packageEntry.descriptor
|
|
78
|
-
: {};
|
|
79
|
-
const optionSchemas = descriptor?.options && typeof descriptor.options === "object"
|
|
80
|
-
? descriptor.options
|
|
81
|
-
: {};
|
|
82
82
|
const subcommandDefinition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
|
|
83
83
|
const positionalArgs = Array.isArray(subcommandDefinition?.positionalArgs)
|
|
84
84
|
? subcommandDefinition.positionalArgs
|
|
85
85
|
: [];
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
const requiredOptionNames = Array.isArray(subcommandDefinition?.requiredOptionNames)
|
|
90
|
-
? subcommandDefinition.requiredOptionNames
|
|
91
|
-
: [];
|
|
92
|
-
if (requiredOptionNames.some((optionName) => String(optionName || "").trim().length > 0)) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const optionNames = Array.isArray(subcommandDefinition?.optionNames) && subcommandDefinition.optionNames.length > 0
|
|
97
|
-
? subcommandDefinition.optionNames
|
|
98
|
-
: Object.keys(optionSchemas);
|
|
99
|
-
for (const optionName of optionNames) {
|
|
100
|
-
const normalizedOptionName = String(optionName || "").trim();
|
|
101
|
-
if (!normalizedOptionName) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const schema = optionSchemas[normalizedOptionName];
|
|
105
|
-
if (schema && typeof schema === "object" && schema.required === true) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return false;
|
|
86
|
+
return positionalArgs.some((arg) => arg && typeof arg === "object" && arg.required !== false);
|
|
110
87
|
}
|
|
111
88
|
|
|
112
89
|
function collectUnexpectedGeneratorSubcommandOptionNames(packageEntry = {}, subcommandName = "", inlineOptions = {}) {
|
|
@@ -228,6 +205,7 @@ async function runPackageGenerateCommand(
|
|
|
228
205
|
resolvePackageKind,
|
|
229
206
|
resolveGeneratorPrimarySubcommand,
|
|
230
207
|
hasGeneratorSubcommandDefinition,
|
|
208
|
+
loadLockFile,
|
|
231
209
|
readdir,
|
|
232
210
|
validateInlineOptionValuesForPackage,
|
|
233
211
|
runGeneratorSubcommand,
|
|
@@ -389,6 +367,15 @@ async function runPackageGenerateCommand(
|
|
|
389
367
|
appRoot,
|
|
390
368
|
optionNames: validatedOptionNames
|
|
391
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
|
+
}
|
|
392
379
|
|
|
393
380
|
const primarySubcommand = resolveGeneratorPrimarySubcommand(packageEntry);
|
|
394
381
|
if (
|
|
@@ -461,7 +448,18 @@ async function runPackageGenerateCommand(
|
|
|
461
448
|
});
|
|
462
449
|
}
|
|
463
450
|
|
|
464
|
-
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
|
+
|
|
465
463
|
renderGeneratePackageHelp({
|
|
466
464
|
io,
|
|
467
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);
|
|
@@ -120,8 +129,9 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
120
129
|
if (hasInlineValue) {
|
|
121
130
|
optionValueRaw = withoutPrefix.slice(withoutPrefix.indexOf("=") + 1);
|
|
122
131
|
} else {
|
|
123
|
-
const
|
|
124
|
-
|
|
132
|
+
const hasNextStringToken = typeof args[0] === "string";
|
|
133
|
+
const nextToken = hasNextStringToken ? String(args[0]) : "";
|
|
134
|
+
if (hasNextStringToken && !nextToken.startsWith("-")) {
|
|
125
135
|
optionValueRaw = args.shift();
|
|
126
136
|
}
|
|
127
137
|
}
|