@jskit-ai/jskit-cli 0.2.64 → 0.2.65
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 +2 -20
- package/src/server/cliRuntime/mutations/textMutations.js +0 -74
- package/src/server/cliRuntime/packageInstallFlow.js +47 -0
- package/src/server/commandHandlers/appCommandCatalog.js +7 -7
- package/src/server/commandHandlers/appCommands/adoptManagedScripts.js +10 -10
- package/src/server/commandHandlers/packageCommands/migrations.js +24 -7
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.65",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.64",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.56",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.55"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "20.x"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { buildCrudFieldContractMap } from "@jskit-ai/kernel/shared/support/crudFieldContract";
|
|
4
5
|
import {
|
|
5
6
|
buildAppCommandOptionMeta,
|
|
6
7
|
listAppCommandDefinitions
|
|
@@ -659,26 +660,7 @@ async function discoverResourceDisplayFields(appRoot, resourceFile = "") {
|
|
|
659
660
|
if (!resource || typeof resource !== "object") {
|
|
660
661
|
return [];
|
|
661
662
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const outputSchemaProperties = resource?.operations?.view?.outputValidator?.schema?.properties;
|
|
665
|
-
if (outputSchemaProperties && typeof outputSchemaProperties === "object") {
|
|
666
|
-
for (const key of Object.keys(outputSchemaProperties)) {
|
|
667
|
-
if (key === resource?.contract?.lookup?.containerKey) {
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
fieldKeys.add(key);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
for (const fieldMeta of Array.isArray(resource?.fieldMeta) ? resource.fieldMeta : []) {
|
|
675
|
-
const key = normalizeText(fieldMeta?.key);
|
|
676
|
-
if (key) {
|
|
677
|
-
fieldKeys.add(key);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
return uniqueSorted([...fieldKeys]);
|
|
663
|
+
return uniqueSorted(Object.keys(buildCrudFieldContractMap(resource)));
|
|
682
664
|
} catch {
|
|
683
665
|
return [];
|
|
684
666
|
}
|
|
@@ -30,80 +30,11 @@ import {
|
|
|
30
30
|
} from "./templateContext.js";
|
|
31
31
|
import { normalizeMutationRelativeFilePath } from "./mutationPathUtils.js";
|
|
32
32
|
|
|
33
|
-
const SETTINGS_FIELDS_CONTRACT_TARGETS = Object.freeze({
|
|
34
|
-
"packages/main/src/shared/resources/consoleSettingsFields.js": Object.freeze({
|
|
35
|
-
contractId: "console.settings-fields.v1",
|
|
36
|
-
marker: "@jskit-contract console.settings-fields.v1",
|
|
37
|
-
requiredSnippets: Object.freeze([
|
|
38
|
-
"defineField",
|
|
39
|
-
"resetConsoleSettingsFields"
|
|
40
|
-
])
|
|
41
|
-
}),
|
|
42
|
-
"packages/main/src/shared/resources/workspaceSettingsFields.js": Object.freeze({
|
|
43
|
-
contractId: "users.settings-fields.workspace.v1",
|
|
44
|
-
marker: "@jskit-contract users.settings-fields.workspace.v1",
|
|
45
|
-
requiredSnippets: Object.freeze([
|
|
46
|
-
"defineField",
|
|
47
|
-
"resetWorkspaceSettingsFields"
|
|
48
|
-
])
|
|
49
|
-
})
|
|
50
|
-
});
|
|
51
33
|
const PRE_FILE_CONFIG_MUTATION_TARGETS = new Set([
|
|
52
34
|
"config/public.js",
|
|
53
35
|
"config/server.js"
|
|
54
36
|
]);
|
|
55
37
|
|
|
56
|
-
function resolveSettingsFieldsContractTarget(relativeFile = "") {
|
|
57
|
-
const normalizedRelativeFile = normalizeMutationRelativeFilePath(relativeFile);
|
|
58
|
-
if (!normalizedRelativeFile) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
const target = SETTINGS_FIELDS_CONTRACT_TARGETS[normalizedRelativeFile];
|
|
62
|
-
if (!target) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
normalizedRelativeFile,
|
|
67
|
-
target
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function validateSettingsFieldsContractMutationTarget({
|
|
72
|
-
appRoot,
|
|
73
|
-
relativeFile,
|
|
74
|
-
packageId
|
|
75
|
-
} = {}) {
|
|
76
|
-
const contractTarget = resolveSettingsFieldsContractTarget(relativeFile);
|
|
77
|
-
if (!contractTarget) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { normalizedRelativeFile, target } = contractTarget;
|
|
82
|
-
const absoluteFile = path.join(appRoot, normalizedRelativeFile);
|
|
83
|
-
const existing = await readFileBufferIfExists(absoluteFile);
|
|
84
|
-
if (!existing.exists) {
|
|
85
|
-
throw createCliError(
|
|
86
|
-
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} is missing. ` +
|
|
87
|
-
`Install @jskit-ai/console-core to scaffold ${target.contractId}.`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const source = existing.buffer.toString("utf8");
|
|
92
|
-
if (!source.includes(target.marker)) {
|
|
93
|
-
throw createCliError(
|
|
94
|
-
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} is missing contract marker "${target.marker}".`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
for (const snippet of target.requiredSnippets) {
|
|
98
|
-
if (source.includes(snippet)) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
throw createCliError(
|
|
102
|
-
`Invalid append-text mutation in ${packageId}: ${normalizedRelativeFile} must include "${snippet}" for ${target.contractId}.`
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
38
|
async function applyTextMutations(packageEntry, appRoot, textMutations, options, managedText, touchedFiles) {
|
|
108
39
|
for (const mutation of textMutations) {
|
|
109
40
|
const when = normalizeMutationWhen(mutation?.when);
|
|
@@ -167,11 +98,6 @@ async function applyTextMutations(packageEntry, appRoot, textMutations, options,
|
|
|
167
98
|
if (position !== "top" && position !== "bottom") {
|
|
168
99
|
throw createCliError(`Invalid append-text mutation in ${packageEntry.packageId}: "position" must be "top" or "bottom".`);
|
|
169
100
|
}
|
|
170
|
-
await validateSettingsFieldsContractMutationTarget({
|
|
171
|
-
appRoot,
|
|
172
|
-
relativeFile,
|
|
173
|
-
packageId: packageEntry.packageId
|
|
174
|
-
});
|
|
175
101
|
|
|
176
102
|
const absoluteFile = path.join(appRoot, relativeFile);
|
|
177
103
|
const previous = await readFileBufferIfExists(absoluteFile);
|
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
applyPackageJsonField,
|
|
19
19
|
removePackageJsonField
|
|
20
20
|
} from "./appState.js";
|
|
21
|
+
import {
|
|
22
|
+
loadMutationWhenConfigContext
|
|
23
|
+
} from "./ioAndMigrations.js";
|
|
21
24
|
import {
|
|
22
25
|
isGeneratorPackageEntry,
|
|
23
26
|
loadAppLocalPackageRegistry
|
|
@@ -82,6 +85,44 @@ function cloneManagedArray(value = []) {
|
|
|
82
85
|
}));
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
function normalizeModeToken(value = "") {
|
|
89
|
+
return String(value || "").trim().toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isWorkspaceCapableTenancyMode(value = "") {
|
|
93
|
+
const normalized = normalizeModeToken(value);
|
|
94
|
+
return normalized === "personal" || normalized === "workspaces";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function collectInstallWarnings({
|
|
98
|
+
packageEntry,
|
|
99
|
+
appRoot,
|
|
100
|
+
appPackageJson
|
|
101
|
+
}) {
|
|
102
|
+
const warnings = [];
|
|
103
|
+
|
|
104
|
+
if (packageEntry?.packageId !== "@jskit-ai/users-core") {
|
|
105
|
+
return warnings;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const configContext = await loadMutationWhenConfigContext(appRoot);
|
|
109
|
+
const tenancyMode = normalizeModeToken(ensureObject(configContext).merged?.tenancyMode);
|
|
110
|
+
const runtimeDependencies = ensureObject(appPackageJson.dependencies);
|
|
111
|
+
const devDependencies = ensureObject(appPackageJson.devDependencies);
|
|
112
|
+
const hasWorkspacesCore = Boolean(
|
|
113
|
+
runtimeDependencies["@jskit-ai/workspaces-core"] || devDependencies["@jskit-ai/workspaces-core"]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (isWorkspaceCapableTenancyMode(tenancyMode) && !hasWorkspacesCore) {
|
|
117
|
+
warnings.push(
|
|
118
|
+
`users-core selected the workspace users scaffold because config.tenancyMode is "${tenancyMode}". ` +
|
|
119
|
+
'Install @jskit-ai/workspaces-core so the app gets the required "app" and "admin" surfaces and workspace helpers.'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return warnings;
|
|
124
|
+
}
|
|
125
|
+
|
|
85
126
|
function resolveManagedSourceRecord(packageEntry, existingInstall = {}) {
|
|
86
127
|
const existingSource = ensureObject(existingInstall.source);
|
|
87
128
|
if (Object.keys(existingSource).length > 0) {
|
|
@@ -483,6 +524,12 @@ async function applyPackageInstall({
|
|
|
483
524
|
touchedFiles
|
|
484
525
|
);
|
|
485
526
|
|
|
527
|
+
mutationWarnings.push(...await collectInstallWarnings({
|
|
528
|
+
packageEntry,
|
|
529
|
+
appRoot,
|
|
530
|
+
appPackageJson
|
|
531
|
+
}));
|
|
532
|
+
|
|
486
533
|
if (generatorPackage) {
|
|
487
534
|
delete lock.installedPackages[packageEntry.packageId];
|
|
488
535
|
} else {
|
|
@@ -5,7 +5,7 @@ const APP_SCRIPT_WRAPPERS = Object.freeze({
|
|
|
5
5
|
release: "jskit app release"
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const COPIED_APP_SCRIPT_VALUES = Object.freeze({
|
|
9
9
|
verify: Object.freeze([
|
|
10
10
|
"npm run lint && npm run test && npm run test:client && npm run build && npx jskit doctor"
|
|
11
11
|
]),
|
|
@@ -20,7 +20,7 @@ const LEGACY_APP_SCRIPT_VALUES = Object.freeze({
|
|
|
20
20
|
])
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const COPIED_APP_SCRIPT_FILES = Object.freeze([
|
|
24
24
|
"scripts/update-jskit-packages.sh",
|
|
25
25
|
"scripts/link-local-jskit-packages.sh",
|
|
26
26
|
"scripts/release.sh"
|
|
@@ -120,7 +120,7 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
120
120
|
}),
|
|
121
121
|
"adopt-managed-scripts": Object.freeze({
|
|
122
122
|
name: "adopt-managed-scripts",
|
|
123
|
-
summary: "Rewrite
|
|
123
|
+
summary: "Rewrite copied scaffolded maintenance scripts to the managed jskit app wrappers.",
|
|
124
124
|
usage: "jskit app adopt-managed-scripts [--dry-run] [--force]",
|
|
125
125
|
options: Object.freeze([
|
|
126
126
|
Object.freeze({
|
|
@@ -129,13 +129,13 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
129
129
|
}),
|
|
130
130
|
Object.freeze({
|
|
131
131
|
label: "--force",
|
|
132
|
-
description: "Replace customized script values too, and remove
|
|
132
|
+
description: "Replace customized script values too, and remove copied maintenance scripts if they exist."
|
|
133
133
|
})
|
|
134
134
|
]),
|
|
135
135
|
defaults: Object.freeze([
|
|
136
136
|
"Known scaffolded script values are rewritten automatically.",
|
|
137
137
|
"Customized script values are reported and left alone unless --force is used.",
|
|
138
|
-
"This command is
|
|
138
|
+
"This command is for apps that still carry copied JSKIT maintenance scripts."
|
|
139
139
|
])
|
|
140
140
|
})
|
|
141
141
|
});
|
|
@@ -190,8 +190,8 @@ function buildAppCommandOptionMeta(subcommandName = "") {
|
|
|
190
190
|
|
|
191
191
|
export {
|
|
192
192
|
APP_SCRIPT_WRAPPERS,
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
COPIED_APP_SCRIPT_VALUES,
|
|
194
|
+
COPIED_APP_SCRIPT_FILES,
|
|
195
195
|
APP_COMMAND_DEFINITIONS,
|
|
196
196
|
listAppCommandDefinitions,
|
|
197
197
|
resolveAppCommandDefinition,
|
|
@@ -2,8 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import {
|
|
4
4
|
APP_SCRIPT_WRAPPERS,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
COPIED_APP_SCRIPT_FILES,
|
|
6
|
+
COPIED_APP_SCRIPT_VALUES
|
|
7
7
|
} from "../appCommandCatalog.js";
|
|
8
8
|
import { fileExists, isTruthyFlag } from "./shared.js";
|
|
9
9
|
|
|
@@ -22,10 +22,10 @@ function shouldRewriteScript(currentValue = "", scriptName = "", force = false)
|
|
|
22
22
|
reason: "already-current"
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
if ((
|
|
25
|
+
if ((COPIED_APP_SCRIPT_VALUES[scriptName] || []).includes(normalizedCurrentValue)) {
|
|
26
26
|
return {
|
|
27
27
|
rewrite: true,
|
|
28
|
-
reason: "
|
|
28
|
+
reason: "copied"
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
if (force) {
|
|
@@ -84,12 +84,12 @@ async function runAppAdoptManagedScriptsCommand(ctx = {}, { appRoot = "", option
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const
|
|
87
|
+
const removableCopiedFiles = [];
|
|
88
88
|
if (force) {
|
|
89
|
-
for (const relativePath of
|
|
89
|
+
for (const relativePath of COPIED_APP_SCRIPT_FILES) {
|
|
90
90
|
const absolutePath = path.join(appRoot, relativePath);
|
|
91
91
|
if (await fileExists(absolutePath)) {
|
|
92
|
-
|
|
92
|
+
removableCopiedFiles.push({
|
|
93
93
|
relativePath,
|
|
94
94
|
absolutePath
|
|
95
95
|
});
|
|
@@ -102,12 +102,12 @@ async function runAppAdoptManagedScriptsCommand(ctx = {}, { appRoot = "", option
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
if (!dryRun && force) {
|
|
105
|
-
for (const { absolutePath } of
|
|
105
|
+
for (const { absolutePath } of removableCopiedFiles) {
|
|
106
106
|
await rm(absolutePath, { recursive: true, force: true });
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
if (changedScripts.length < 1 && skippedScripts.length < 1 &&
|
|
110
|
+
if (changedScripts.length < 1 && skippedScripts.length < 1 && removableCopiedFiles.length < 1) {
|
|
111
111
|
stdout.write("[adopt-managed-scripts] package.json already uses the managed JSKIT wrappers.\n");
|
|
112
112
|
return 0;
|
|
113
113
|
}
|
|
@@ -118,7 +118,7 @@ async function runAppAdoptManagedScriptsCommand(ctx = {}, { appRoot = "", option
|
|
|
118
118
|
for (const record of skippedScripts) {
|
|
119
119
|
stdout.write(`[adopt-managed-scripts] kept customized script ${record.scriptName}: ${record.currentValue}\n`);
|
|
120
120
|
}
|
|
121
|
-
for (const record of
|
|
121
|
+
for (const record of removableCopiedFiles) {
|
|
122
122
|
stdout.write(`[adopt-managed-scripts] ${dryRun ? "would remove" : "removed"} ${record.relativePath}\n`);
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -128,16 +128,33 @@ async function runPackageMigrationsCommand(ctx = {}, { positional, options, cwd,
|
|
|
128
128
|
warnings: migrationWarnings
|
|
129
129
|
}, null, 2)}\n`);
|
|
130
130
|
} else {
|
|
131
|
-
io.stdout.write(`Generated migrations (${scope}).\n`);
|
|
132
|
-
io.stdout.write(`
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
io.stdout.write(`Generated managed migrations (${scope}).\n`);
|
|
132
|
+
io.stdout.write(`Packages needing migration sync (${requestedPackageIds.length}):\n`);
|
|
133
|
+
if (requestedPackageIds.length > 0) {
|
|
134
|
+
for (const packageId of requestedPackageIds) {
|
|
135
|
+
io.stdout.write(`- ${packageId}\n`);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
io.stdout.write("- none\n");
|
|
139
|
+
io.stdout.write(
|
|
140
|
+
" Installed packages are already migration-synced, or they do not ship JSKIT-managed migrations.\n"
|
|
141
|
+
);
|
|
135
142
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
|
|
144
|
+
io.stdout.write(`Migration files written (${touchedFileList.length}):\n`);
|
|
145
|
+
if (touchedFileList.length > 0) {
|
|
146
|
+
for (const touchedFile of touchedFileList) {
|
|
147
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
io.stdout.write("- none\n");
|
|
151
|
+
io.stdout.write(" No managed migration files were written in this run.\n");
|
|
139
152
|
}
|
|
153
|
+
|
|
140
154
|
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
155
|
+
io.stdout.write(
|
|
156
|
+
"Reminder: this command only writes or refreshes JSKIT-managed migration files. Run `npm run db:migrate` separately to apply pending migrations to the database.\n"
|
|
157
|
+
);
|
|
141
158
|
if (options.verbose && migrationWarnings.length > 0) {
|
|
142
159
|
io.stdout.write(`Warnings (${migrationWarnings.length}):\n`);
|
|
143
160
|
for (const warning of migrationWarnings) {
|