@nocobase/cli 2.1.0-beta.44.test.4 → 2.1.0-beta.45

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.
Files changed (60) hide show
  1. package/dist/commands/source/download.js +1 -1
  2. package/dist/lib/api-client.js +335 -0
  3. package/dist/lib/api-command-compat.js +641 -0
  4. package/dist/lib/app-health.js +139 -0
  5. package/dist/lib/app-managed-resources.js +332 -0
  6. package/dist/lib/app-public-path.js +80 -0
  7. package/dist/lib/app-runtime.js +189 -0
  8. package/dist/lib/auth-store.js +520 -0
  9. package/dist/lib/backup.js +171 -0
  10. package/dist/lib/bootstrap.js +409 -0
  11. package/dist/lib/build-config.js +18 -0
  12. package/dist/lib/builtin-db.js +86 -0
  13. package/dist/lib/cli-config.js +497 -0
  14. package/dist/lib/cli-entry-error.js +52 -0
  15. package/dist/lib/cli-home.js +47 -0
  16. package/dist/lib/cli-locale.js +141 -0
  17. package/dist/lib/command-discovery.js +39 -0
  18. package/dist/lib/command-log.js +284 -0
  19. package/dist/lib/db-connection-check.js +219 -0
  20. package/dist/lib/docker-env-file.js +60 -0
  21. package/dist/lib/docker-image.js +37 -0
  22. package/dist/lib/docker-log-stream.js +45 -0
  23. package/dist/lib/env-auth.js +963 -0
  24. package/dist/lib/env-command-config.js +45 -0
  25. package/dist/lib/env-config.js +100 -0
  26. package/dist/lib/env-guard.js +61 -0
  27. package/dist/lib/env-paths.js +101 -0
  28. package/dist/lib/env-proxy.js +1295 -0
  29. package/dist/lib/generated-command.js +203 -0
  30. package/dist/lib/http-request.js +49 -0
  31. package/dist/lib/inquirer-theme.js +17 -0
  32. package/dist/lib/inquirer.js +243 -0
  33. package/dist/lib/managed-env-file.js +98 -0
  34. package/dist/lib/naming.js +70 -0
  35. package/dist/lib/object-utils.js +76 -0
  36. package/dist/lib/openapi.js +62 -0
  37. package/dist/lib/plugin-import.js +279 -0
  38. package/dist/lib/plugin-storage.js +64 -0
  39. package/dist/lib/post-processors.js +23 -0
  40. package/dist/lib/prompt-catalog-core.js +186 -0
  41. package/dist/lib/prompt-catalog-terminal.js +375 -0
  42. package/dist/lib/prompt-catalog.js +10 -0
  43. package/dist/lib/prompt-validators.js +272 -0
  44. package/dist/lib/prompt-web-ui.js +2234 -0
  45. package/dist/lib/resource-command.js +357 -0
  46. package/dist/lib/resource-request.js +104 -0
  47. package/dist/lib/run-npm.js +429 -0
  48. package/dist/lib/runtime-env-vars.js +32 -0
  49. package/dist/lib/runtime-generator.js +498 -0
  50. package/dist/lib/runtime-store.js +56 -0
  51. package/dist/lib/self-manager.js +301 -0
  52. package/dist/lib/session-id.js +17 -0
  53. package/dist/lib/session-integration.js +703 -0
  54. package/dist/lib/session-store.js +118 -0
  55. package/dist/lib/skills-manager.js +438 -0
  56. package/dist/lib/source-publish.js +326 -0
  57. package/dist/lib/source-registry.js +188 -0
  58. package/dist/lib/startup-update.js +309 -0
  59. package/dist/lib/ui.js +159 -0
  60. package/package.json +3 -2
@@ -0,0 +1,76 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export function pickKeys(object, keys) {
10
+ const picked = {};
11
+ for (const key of keys) {
12
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
13
+ const typedKey = key;
14
+ picked[typedKey] = object[typedKey];
15
+ }
16
+ }
17
+ return picked;
18
+ }
19
+ export function omitKeys(object, keys) {
20
+ const omittedKeys = new Set(keys);
21
+ const result = {};
22
+ for (const key of Object.keys(object)) {
23
+ if (!omittedKeys.has(key)) {
24
+ const typedKey = key;
25
+ result[typedKey] = object[typedKey];
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+ export function upperFirst(value) {
31
+ if (!value) {
32
+ return value;
33
+ }
34
+ return value[0].toUpperCase() + value.slice(1);
35
+ }
36
+ export function deepEqual(left, right) {
37
+ if (Object.is(left, right)) {
38
+ return true;
39
+ }
40
+ if (left == null || right == null) {
41
+ return false;
42
+ }
43
+ if (typeof left !== typeof right) {
44
+ return false;
45
+ }
46
+ if (typeof left !== 'object') {
47
+ return false;
48
+ }
49
+ if (Array.isArray(left) || Array.isArray(right)) {
50
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
51
+ return false;
52
+ }
53
+ for (let index = 0; index < left.length; index += 1) {
54
+ if (!deepEqual(left[index], right[index])) {
55
+ return false;
56
+ }
57
+ }
58
+ return true;
59
+ }
60
+ const leftObject = left;
61
+ const rightObject = right;
62
+ const leftKeys = Object.keys(leftObject);
63
+ const rightKeys = Object.keys(rightObject);
64
+ if (leftKeys.length !== rightKeys.length) {
65
+ return false;
66
+ }
67
+ for (const key of leftKeys) {
68
+ if (!Object.prototype.hasOwnProperty.call(rightObject, key)) {
69
+ return false;
70
+ }
71
+ if (!deepEqual(leftObject[key], rightObject[key])) {
72
+ return false;
73
+ }
74
+ }
75
+ return true;
76
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
10
+ function resolveLocalRef(document, ref) {
11
+ if (!ref.startsWith('#/')) {
12
+ return undefined;
13
+ }
14
+ return ref
15
+ .slice(2)
16
+ .split('/')
17
+ .reduce((current, segment) => current?.[segment], document);
18
+ }
19
+ function dereferenceNode(node, document, seen = new Set()) {
20
+ if (Array.isArray(node)) {
21
+ return node.map((item) => dereferenceNode(item, document, seen));
22
+ }
23
+ if (!node || typeof node !== 'object') {
24
+ return node;
25
+ }
26
+ const ref = node.$ref;
27
+ if (typeof ref === 'string') {
28
+ if (seen.has(ref)) {
29
+ return {};
30
+ }
31
+ const resolved = resolveLocalRef(document, ref);
32
+ if (!resolved) {
33
+ return node;
34
+ }
35
+ return dereferenceNode(resolved, document, new Set([...seen, ref]));
36
+ }
37
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, dereferenceNode(value, document, seen)]));
38
+ }
39
+ export function collectOperations(document) {
40
+ const operations = [];
41
+ for (const [pathTemplate, pathItem] of Object.entries(document.paths ?? {})) {
42
+ for (const method of HTTP_METHODS) {
43
+ const operation = pathItem?.[method];
44
+ if (!operation || '$ref' in operation) {
45
+ continue;
46
+ }
47
+ const parameters = [...(pathItem.parameters ?? []), ...(operation.parameters ?? [])]
48
+ .map((parameter) => dereferenceNode(parameter, document))
49
+ .filter((parameter) => Boolean(parameter && !('$ref' in parameter)));
50
+ operations.push({
51
+ method,
52
+ pathTemplate,
53
+ operation: {
54
+ ...operation,
55
+ parameters,
56
+ requestBody: operation.requestBody ? dereferenceNode(operation.requestBody, document) : undefined,
57
+ },
58
+ });
59
+ }
60
+ }
61
+ return operations;
62
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import fs from 'node:fs';
10
+ import fsp from 'node:fs/promises';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { Readable } from 'node:stream';
14
+ import { pipeline } from 'node:stream/promises';
15
+ import { createGunzip } from 'node:zlib';
16
+ import * as tar from 'tar';
17
+ import { fetchWithPreservedAuthRedirect } from './http-request.js';
18
+ import { resolvePluginStoragePath } from './plugin-storage.js';
19
+ import { run } from './run-npm.js';
20
+ const NPM_PACK_TIMEOUT_MS = 30_000;
21
+ const NPM_AUTH_ERROR_PATTERNS = [
22
+ 'e401',
23
+ 'e403',
24
+ 'unauthorized',
25
+ 'authorization',
26
+ 'forbidden',
27
+ 'need auth',
28
+ 'requires authentication',
29
+ 'unable to authenticate',
30
+ 'authentication token',
31
+ ];
32
+ const NPM_NOT_FOUND_ERROR_PATTERNS = [
33
+ 'e404',
34
+ 'etarget',
35
+ 'not found',
36
+ 'not in this registry',
37
+ 'no matching version found',
38
+ 'no match found',
39
+ ];
40
+ const NPM_NETWORK_ERROR_PATTERNS = [
41
+ 'enotfound',
42
+ 'eai_again',
43
+ 'etimedout',
44
+ 'esockettimedout',
45
+ 'econnreset',
46
+ 'econnrefused',
47
+ 'enetunreach',
48
+ 'ehostunreach',
49
+ 'fetch failed',
50
+ 'getaddrinfo',
51
+ 'timed out',
52
+ ];
53
+ function trimValue(value) {
54
+ const text = String(value ?? '').trim();
55
+ return text || undefined;
56
+ }
57
+ async function pathExists(target) {
58
+ try {
59
+ await fsp.access(target);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ function isHttpArchiveSource(value) {
67
+ try {
68
+ const url = new URL(value);
69
+ return url.protocol === 'http:' || url.protocol === 'https:';
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ function responseBodyToNodeReadable(body) {
76
+ return Readable.fromWeb(body);
77
+ }
78
+ function looksLikeLocalArchivePath(value) {
79
+ return (path.isAbsolute(value) ||
80
+ value.startsWith('./') ||
81
+ value.startsWith('../') ||
82
+ value.endsWith('.tgz') ||
83
+ value.endsWith('.tar.gz'));
84
+ }
85
+ function resolvePluginOutputDir(storagePluginsPath, packageName) {
86
+ const outputDir = path.resolve(storagePluginsPath, packageName);
87
+ const relative = path.relative(storagePluginsPath, outputDir);
88
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
89
+ throw new Error(`Imported archive package name "${packageName}" resolves outside plugin storage.`);
90
+ }
91
+ return outputDir;
92
+ }
93
+ function normalizeNpmRegistry(value) {
94
+ const text = trimValue(value);
95
+ return text ? text.replace(/\/+$/, '') : undefined;
96
+ }
97
+ function formatNpmPackFailureMessage(source, registry, details) {
98
+ const normalizedDetails = details.toLowerCase();
99
+ const lines = [
100
+ `Failed to import npm package "${source}" with \`npm pack\`.`,
101
+ details,
102
+ ];
103
+ const loginCommand = registry ? `npm login --registry=${registry}` : 'npm login';
104
+ if (NPM_AUTH_ERROR_PATTERNS.some((pattern) => normalizedDetails.includes(pattern))) {
105
+ lines.push(`Hint: If this is a private registry, run \`${loginCommand}\` first and retry.`);
106
+ return lines.join('\n');
107
+ }
108
+ if (NPM_NOT_FOUND_ERROR_PATTERNS.some((pattern) => normalizedDetails.includes(pattern))) {
109
+ lines.push(registry
110
+ ? `Hint: Check that "${source}" exists in ${registry} and that the package name or tag is correct.`
111
+ : `Hint: Check that "${source}" exists and that the package name or tag is correct.`);
112
+ return lines.join('\n');
113
+ }
114
+ if (NPM_NETWORK_ERROR_PATTERNS.some((pattern) => normalizedDetails.includes(pattern))) {
115
+ lines.push(registry
116
+ ? `Hint: Check that the npm registry ${registry} is reachable from this machine.`
117
+ : 'Hint: Check that the npm registry is reachable from this machine.');
118
+ return lines.join('\n');
119
+ }
120
+ if (registry) {
121
+ lines.push(`Hint: If this is a private registry, make sure \`${loginCommand}\` has completed successfully.`);
122
+ }
123
+ return lines.join('\n');
124
+ }
125
+ async function resolvePackedPluginTarball(packRoot, sourceLabel) {
126
+ const entries = await fsp.readdir(packRoot, { withFileTypes: true });
127
+ const tarballs = entries
128
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.tgz'))
129
+ .map((entry) => path.join(packRoot, entry.name))
130
+ .sort();
131
+ if (tarballs.length === 1) {
132
+ return tarballs[0];
133
+ }
134
+ if (tarballs.length === 0) {
135
+ throw new Error(`npm pack did not produce a local tarball for ${sourceLabel}.`);
136
+ }
137
+ throw new Error(`npm pack produced multiple tarballs for ${sourceLabel}.`);
138
+ }
139
+ async function packNpmPluginSource(source, npmRegistry, runFn = run) {
140
+ const packRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'nocobase-cli-plugin-pack-'));
141
+ const args = ['pack', '--silent'];
142
+ const registry = normalizeNpmRegistry(npmRegistry);
143
+ if (registry) {
144
+ args.push(`--registry=${registry}`);
145
+ }
146
+ args.push(source);
147
+ let stdout = '';
148
+ let stderr = '';
149
+ try {
150
+ await runFn('npm', args, {
151
+ cwd: packRoot,
152
+ stdio: 'pipe',
153
+ errorName: 'npm pack',
154
+ timeoutMs: NPM_PACK_TIMEOUT_MS,
155
+ onStdout: (chunk) => {
156
+ stdout += chunk;
157
+ },
158
+ onStderr: (chunk) => {
159
+ stderr += chunk;
160
+ },
161
+ });
162
+ const tarballPath = await resolvePackedPluginTarball(packRoot, source);
163
+ return {
164
+ stream: fs.createReadStream(tarballPath),
165
+ source,
166
+ sourceType: 'npm',
167
+ cleanup: async () => {
168
+ await fsp.rm(packRoot, { recursive: true, force: true });
169
+ },
170
+ };
171
+ }
172
+ catch (error) {
173
+ await fsp.rm(packRoot, { recursive: true, force: true });
174
+ const originalMessage = error instanceof Error ? error.message : String(error);
175
+ const details = trimValue(stderr) || trimValue(stdout) || trimValue(originalMessage) || 'npm pack failed.';
176
+ throw new Error(formatNpmPackFailureMessage(source, registry, details));
177
+ }
178
+ }
179
+ async function openPluginSource(source, npmRegistry, runFn = run) {
180
+ if (isHttpArchiveSource(source)) {
181
+ const response = await fetchWithPreservedAuthRedirect(source);
182
+ if (!response.ok) {
183
+ const statusText = trimValue(response.statusText);
184
+ throw new Error(`Failed to download plugin archive from ${source}: ${response.status}${statusText ? ` ${statusText}` : ''}.`);
185
+ }
186
+ if (!response.body) {
187
+ throw new Error(`Downloaded plugin archive from ${source} does not include a response body.`);
188
+ }
189
+ return {
190
+ stream: responseBodyToNodeReadable(response.body),
191
+ source,
192
+ sourceType: 'url',
193
+ cleanup: async () => undefined,
194
+ };
195
+ }
196
+ const resolvedPath = path.resolve(process.cwd(), source);
197
+ if (await pathExists(resolvedPath)) {
198
+ return {
199
+ stream: fs.createReadStream(resolvedPath),
200
+ source: resolvedPath,
201
+ sourceType: 'file',
202
+ cleanup: async () => undefined,
203
+ };
204
+ }
205
+ if (looksLikeLocalArchivePath(source)) {
206
+ throw new Error(`Plugin archive file does not exist: ${resolvedPath}`);
207
+ }
208
+ return await packNpmPluginSource(source, npmRegistry, runFn);
209
+ }
210
+ async function readPluginMetadata(extractRoot, sourceLabel) {
211
+ const packageJsonPath = path.join(extractRoot, 'package.json');
212
+ let content;
213
+ try {
214
+ content = await fsp.readFile(packageJsonPath, 'utf8');
215
+ }
216
+ catch {
217
+ throw new Error(`Imported archive from ${sourceLabel} does not contain a package.json at the package root.`);
218
+ }
219
+ let metadata;
220
+ try {
221
+ metadata = JSON.parse(content);
222
+ }
223
+ catch {
224
+ throw new Error(`Imported archive from ${sourceLabel} contains an invalid package.json.`);
225
+ }
226
+ const packageName = trimValue(metadata.name);
227
+ if (!packageName) {
228
+ throw new Error(`Imported archive from ${sourceLabel} is missing "package.json.name".`);
229
+ }
230
+ return {
231
+ packageName,
232
+ packageVersion: trimValue(metadata.version),
233
+ };
234
+ }
235
+ export async function importPluginSource(source, options = {}) {
236
+ const normalizedSource = trimValue(source);
237
+ if (!normalizedSource) {
238
+ throw new Error('Pass a plugin archive path, URL, or npm package spec.');
239
+ }
240
+ const storagePluginsPath = resolvePluginStoragePath(options.storagePath);
241
+ await fsp.mkdir(storagePluginsPath, { recursive: true });
242
+ const archive = await openPluginSource(normalizedSource, options.npmRegistry, options.runFn);
243
+ const stageDir = await fsp.mkdtemp(path.join(storagePluginsPath, '.nb-plugin-import-'));
244
+ let stageMoved = false;
245
+ try {
246
+ try {
247
+ await pipeline(archive.stream, createGunzip(), tar.extract({ cwd: stageDir, strip: 1 }));
248
+ }
249
+ catch (error) {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ throw new Error(`Failed to extract plugin archive from ${archive.source}: ${message}`);
252
+ }
253
+ const { packageName, packageVersion } = await readPluginMetadata(stageDir, archive.source);
254
+ const outputDir = resolvePluginOutputDir(storagePluginsPath, packageName);
255
+ const action = (await pathExists(outputDir)) ? 'updated' : 'installed';
256
+ await fsp.mkdir(path.dirname(outputDir), { recursive: true });
257
+ await fsp.rm(outputDir, { recursive: true, force: true });
258
+ await fsp.rename(stageDir, outputDir);
259
+ stageMoved = true;
260
+ return {
261
+ action,
262
+ packageName,
263
+ packageVersion,
264
+ outputDir,
265
+ source: archive.source,
266
+ sourceType: archive.sourceType,
267
+ storagePluginsPath,
268
+ };
269
+ }
270
+ finally {
271
+ if (!stageMoved) {
272
+ await fsp.rm(stageDir, { recursive: true, force: true });
273
+ }
274
+ await archive.cleanup();
275
+ }
276
+ }
277
+ export async function importPluginArchive(source, storagePath) {
278
+ return await importPluginSource(source, { storagePath });
279
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import path from 'node:path';
10
+ import { access, lstat, readlink, rm } from 'node:fs/promises';
11
+ async function pathExists(target) {
12
+ try {
13
+ await access(target);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ export function resolvePluginStoragePath(storagePath) {
21
+ const root = String(storagePath ?? process.env.STORAGE_PATH ?? '').trim();
22
+ if (root) {
23
+ return path.join(path.isAbsolute(root) ? root : path.resolve(process.cwd(), root), 'plugins');
24
+ }
25
+ const configured = String(process.env.PLUGIN_STORAGE_PATH ?? '').trim();
26
+ if (configured) {
27
+ return path.isAbsolute(configured) ? configured : path.resolve(process.cwd(), configured);
28
+ }
29
+ return path.resolve(process.cwd(), 'storage', 'plugins');
30
+ }
31
+ export async function removeStoragePluginSymlink(pluginName, storagePath, nodeModulesPath = String(process.env.NODE_MODULES_PATH ?? '').trim()) {
32
+ if (!nodeModulesPath) {
33
+ return false;
34
+ }
35
+ const storagePluginsPath = resolvePluginStoragePath(storagePath);
36
+ const targetPath = path.resolve(storagePluginsPath, pluginName);
37
+ const linkPath = path.resolve(nodeModulesPath, pluginName);
38
+ if (!(await pathExists(linkPath))) {
39
+ return false;
40
+ }
41
+ let statResult;
42
+ try {
43
+ statResult = await lstat(linkPath);
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ if (!statResult.isSymbolicLink()) {
49
+ return false;
50
+ }
51
+ let resolvedLinkTarget = '';
52
+ try {
53
+ const linkTarget = await readlink(linkPath);
54
+ resolvedLinkTarget = path.resolve(path.dirname(linkPath), linkTarget);
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ if (resolvedLinkTarget !== targetPath) {
60
+ return false;
61
+ }
62
+ await rm(linkPath, { recursive: true, force: true });
63
+ return true;
64
+ }
@@ -0,0 +1,23 @@
1
+ function buildKey(resource, action) {
2
+ return `${resource}:${action}`;
3
+ }
4
+ class PostProcessorRegistry {
5
+ processors = new Map();
6
+ register(resource, action, processor) {
7
+ this.processors.set(buildKey(resource, action), processor);
8
+ }
9
+ resolve(resource, action) {
10
+ if (!resource || !action) {
11
+ return undefined;
12
+ }
13
+ return this.processors.get(buildKey(resource, action));
14
+ }
15
+ }
16
+ export const postProcessorRegistry = new PostProcessorRegistry();
17
+ export async function applyPostProcessor(result, context) {
18
+ const processor = postProcessorRegistry.resolve(context.operation.logicalResourceName, context.operation.actionName);
19
+ if (!processor) {
20
+ return result;
21
+ }
22
+ return processor(result, context);
23
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { createCliTranslate, resolveCliLocale, resolveLocalizedText, } from "./cli-locale.js";
10
+ export function selectOptionValues(options) {
11
+ return options.map((o) => (typeof o === 'string' ? o : o.value));
12
+ }
13
+ export function resolvePromptText(text, locale, fallback = '') {
14
+ return resolveLocalizedText(text, { locale, fallback });
15
+ }
16
+ export function enabledSelectOptionValues(options) {
17
+ return options
18
+ .filter((o) => typeof o === 'string' || o.disabled !== true)
19
+ .map((o) => (typeof o === 'string' ? o : o.value));
20
+ }
21
+ export function createPromptCatalogHooks(locale, overrides, defaults) {
22
+ return {
23
+ onCancel: overrides?.onCancel
24
+ ?? defaults?.onCancel
25
+ ?? (() => {
26
+ throw new Error(createCliTranslate(locale)('promptCatalog.common.cancelled'));
27
+ }),
28
+ onMissingNonInteractive: overrides?.onMissingNonInteractive
29
+ ?? defaults?.onMissingNonInteractive
30
+ ?? ((message) => {
31
+ throw new Error(message);
32
+ }),
33
+ };
34
+ }
35
+ export function hasIvKey(iv, key) {
36
+ return (Object.prototype.hasOwnProperty.call(iv, key) && iv[key] !== undefined && iv[key] !== null);
37
+ }
38
+ export function resolveTextInitial(def, valuesSoFar) {
39
+ const iv = def.initialValue;
40
+ if (typeof iv === 'function') {
41
+ return iv(valuesSoFar);
42
+ }
43
+ return iv;
44
+ }
45
+ export function mergedText(key, def, iv, useYesInitial, valuesSoFar = {}) {
46
+ if (hasIvKey(iv, key)) {
47
+ return String(iv[key]);
48
+ }
49
+ if (useYesInitial && def.yesInitialValue !== undefined) {
50
+ return def.yesInitialValue;
51
+ }
52
+ return resolveTextInitial(def, valuesSoFar) ?? '';
53
+ }
54
+ export function mergedBoolean(key, def, iv, useYesInitial) {
55
+ if (hasIvKey(iv, key)) {
56
+ return Boolean(iv[key]);
57
+ }
58
+ if (useYesInitial && def.yesInitialValue !== undefined) {
59
+ return def.yesInitialValue;
60
+ }
61
+ return def.initialValue ?? true;
62
+ }
63
+ export function mergedSelect(key, def, iv, useYesInitial) {
64
+ const enabledValueList = enabledSelectOptionValues(def.options);
65
+ if (hasIvKey(iv, key)) {
66
+ const s = String(iv[key]);
67
+ if (enabledValueList.includes(s)) {
68
+ return s;
69
+ }
70
+ return undefined;
71
+ }
72
+ if (useYesInitial && def.yesInitialValue !== undefined && enabledValueList.includes(def.yesInitialValue)) {
73
+ return def.yesInitialValue;
74
+ }
75
+ const d = def.initialValue;
76
+ if (d !== undefined && enabledValueList.includes(d)) {
77
+ return d;
78
+ }
79
+ return enabledValueList[0];
80
+ }
81
+ export function mergedInteger(key, def, iv, useYesInitial) {
82
+ if (hasIvKey(iv, key)) {
83
+ const v = iv[key];
84
+ return typeof v === 'number' ? v : Number.parseInt(String(v), 10);
85
+ }
86
+ if (useYesInitial && def.yesInitialValue !== undefined) {
87
+ return def.yesInitialValue;
88
+ }
89
+ return def.initialValue;
90
+ }
91
+ export function mergedPassword(key, def, iv, useYesInitial, valuesSoFar = {}) {
92
+ if (hasIvKey(iv, key)) {
93
+ return String(iv[key] ?? '');
94
+ }
95
+ if (useYesInitial && def.yesInitialValue !== undefined) {
96
+ return def.yesInitialValue;
97
+ }
98
+ const initialValue = def.initialValue;
99
+ return typeof initialValue === 'function' ? initialValue(valuesSoFar) : initialValue;
100
+ }
101
+ export function isBlankText(value) {
102
+ return value.trim() === '';
103
+ }
104
+ export async function runPromptFieldValidate(def, value, values) {
105
+ if (def.type === 'intro' || def.type === 'outro' || def.type === 'run') {
106
+ return undefined;
107
+ }
108
+ if (!('validate' in def) || def.validate === undefined) {
109
+ return undefined;
110
+ }
111
+ const r = await def.validate(value, values);
112
+ if (r == null || r === '') {
113
+ return undefined;
114
+ }
115
+ return String(r);
116
+ }
117
+ export function isPromptBlockSkipped(def, values) {
118
+ if (def.type === 'run') {
119
+ return def.when !== undefined && !def.when(values);
120
+ }
121
+ return def.hidden !== undefined && def.hidden(values);
122
+ }
123
+ export function tryApplyPreset(key, def, preset, out, hooks, locale) {
124
+ const t = createCliTranslate(locale);
125
+ if (!hasIvKey(preset, key)) {
126
+ return false;
127
+ }
128
+ const raw = preset[key];
129
+ switch (def.type) {
130
+ case 'intro':
131
+ case 'outro':
132
+ case 'run':
133
+ return false;
134
+ case 'text': {
135
+ const s = String(raw ?? '');
136
+ if (def.required && isBlankText(s)) {
137
+ hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
138
+ }
139
+ out[key] = s;
140
+ return true;
141
+ }
142
+ case 'boolean': {
143
+ out[key] = Boolean(raw);
144
+ return true;
145
+ }
146
+ case 'select': {
147
+ const valueList = selectOptionValues(def.options);
148
+ const s = String(raw ?? '');
149
+ if (!valueList.includes(s)) {
150
+ hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidSelect', { key, value: s, options: valueList.join(', ') }));
151
+ }
152
+ out[key] = s;
153
+ return true;
154
+ }
155
+ case 'password': {
156
+ const s = String(raw ?? '');
157
+ if (def.required && isBlankText(s)) {
158
+ hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
159
+ }
160
+ out[key] = s;
161
+ return true;
162
+ }
163
+ case 'integer': {
164
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
165
+ out[key] = raw;
166
+ return true;
167
+ }
168
+ const s = String(raw ?? '').trim();
169
+ if (s === '') {
170
+ if (def.required) {
171
+ hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
172
+ }
173
+ out[key] = def.initialValue ?? 0;
174
+ return true;
175
+ }
176
+ if (!/^-?\\d+$/.test(s)) {
177
+ hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidInteger', { key }));
178
+ }
179
+ out[key] = Number.parseInt(s, 10);
180
+ return true;
181
+ }
182
+ }
183
+ }
184
+ export function resolvePromptCatalogLocale(locale) {
185
+ return resolveCliLocale(locale);
186
+ }