@nocobase/cli 2.2.0-beta.1 → 2.2.0-beta.3

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 (37) hide show
  1. package/bin/early-locale.js +89 -0
  2. package/bin/node-version.js +35 -0
  3. package/bin/run.js +9 -0
  4. package/bin/windows-admin.js +60 -0
  5. package/dist/commands/app/destroy.js +4 -3
  6. package/dist/commands/app/shared.js +49 -3
  7. package/dist/commands/examples/prompts-stages.js +2 -2
  8. package/dist/commands/examples/prompts-test.js +2 -2
  9. package/dist/commands/init.js +5 -9
  10. package/dist/commands/license/activate.js +4 -1
  11. package/dist/commands/license/shared.js +24 -15
  12. package/dist/commands/self/check.js +1 -1
  13. package/dist/commands/self/update.js +2 -2
  14. package/dist/commands/skills/check.js +4 -5
  15. package/dist/commands/skills/install.js +18 -1
  16. package/dist/commands/skills/update.js +19 -4
  17. package/dist/commands/source/download.js +1 -2
  18. package/dist/lib/api-command-compat.js +51 -8
  19. package/dist/lib/prompt-web-ui.js +7 -11
  20. package/dist/lib/self-manager.js +3 -0
  21. package/dist/lib/skills-manager.js +116 -23
  22. package/dist/locale/en-US.json +10 -4
  23. package/dist/locale/zh-CN.json +10 -4
  24. package/package.json +7 -2
  25. package/assets/env-proxy/nginx/app.conf.tpl +0 -23
  26. package/assets/env-proxy/nginx/nocobase.conf.tpl +0 -5
  27. package/assets/env-proxy/nginx/snippets/dist-location.conf +0 -5
  28. package/assets/env-proxy/nginx/snippets/gzip.conf +0 -17
  29. package/assets/env-proxy/nginx/snippets/log-format-http.conf +0 -13
  30. package/assets/env-proxy/nginx/snippets/maps-http.conf +0 -14
  31. package/assets/env-proxy/nginx/snippets/mime-types.conf +0 -98
  32. package/assets/env-proxy/nginx/snippets/proxy-location.conf +0 -17
  33. package/assets/env-proxy/nginx/snippets/spa-location.conf +0 -6
  34. package/assets/env-proxy/nginx/snippets/uploads-location.conf +0 -21
  35. package/scripts/build.mjs +0 -34
  36. package/scripts/clean.mjs +0 -9
  37. package/tsconfig.json +0 -19
@@ -16,7 +16,7 @@ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerIm
16
16
  import { run } from "../../lib/run-npm.js";
17
17
  import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
18
18
  const DEFAULT_DOCKER_PLATFORM = 'auto';
19
- const DEFAULT_DOWNLOAD_VERSION = 'beta';
19
+ const DEFAULT_DOWNLOAD_VERSION = 'latest';
20
20
  const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
21
21
  const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
22
22
  function defaultOutputDirForVersion(versionTag) {
@@ -276,7 +276,6 @@ export default class SourceDownload extends Command {
276
276
  value: 'latest',
277
277
  label: downloadText('prompts.version.latestLabel'),
278
278
  hint: downloadText('prompts.version.latestHint'),
279
- disabled: true,
280
279
  },
281
280
  {
282
281
  value: 'beta',
@@ -114,12 +114,22 @@ function compareWithOperator(version, operator, expected) {
114
114
  }
115
115
  function normalizeAppByChannelComparableVersion(version) {
116
116
  const normalized = String(version ?? '').trim();
117
- const match = normalized.match(/^(\d+\.\d+\.\d+)-(alpha|beta|rc)(?:\.(\d+))?(?:\..+)?$/);
117
+ const match = normalized.match(/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-.]+))?$/);
118
118
  if (!match) {
119
119
  return normalized;
120
120
  }
121
- const [, base, channel, sequence] = match;
122
- return sequence ? `${base}-${channel}.${sequence}` : `${base}-${channel}`;
121
+ const [, base, prerelease] = match;
122
+ if (!prerelease) {
123
+ return base;
124
+ }
125
+ const [channel, sequence] = prerelease.split('.');
126
+ if ((channel === 'alpha' || channel === 'beta') && sequence && /^\d+$/.test(sequence)) {
127
+ return `${base}-${channel}.${sequence}`;
128
+ }
129
+ if (channel === 'alpha' || channel === 'beta') {
130
+ return `${base}-${channel}`;
131
+ }
132
+ return base;
123
133
  }
124
134
  function normalizeAppByChannelCondition(condition) {
125
135
  if (!condition) {
@@ -134,6 +144,42 @@ function normalizeAppByChannelCondition(condition) {
134
144
  }
135
145
  return normalized;
136
146
  }
147
+ function compareAppByChannelVersions(version, expected) {
148
+ const comparableVersion = normalizeAppByChannelComparableVersion(version);
149
+ const comparableExpected = normalizeAppByChannelComparableVersion(expected);
150
+ if (comparableVersion === comparableExpected ||
151
+ comparableVersion.startsWith(`${comparableExpected}.`) ||
152
+ comparableVersion.startsWith(`${comparableExpected}-`)) {
153
+ return 0;
154
+ }
155
+ return compareVersions(comparableVersion, comparableExpected);
156
+ }
157
+ function compareAppByChannelWithOperator(version, operator, expected) {
158
+ const compared = compareAppByChannelVersions(version, expected);
159
+ switch (operator) {
160
+ case 'eq':
161
+ return compared === 0;
162
+ case 'gt':
163
+ return compared > 0;
164
+ case 'gte':
165
+ return compared >= 0;
166
+ case 'lt':
167
+ return compared < 0;
168
+ case 'lte':
169
+ return compared <= 0;
170
+ default:
171
+ return false;
172
+ }
173
+ }
174
+ function matchesAppByChannelVersionCondition(version, condition) {
175
+ if (!condition) {
176
+ return true;
177
+ }
178
+ return ['eq', 'gt', 'gte', 'lt', 'lte'].every((operator) => {
179
+ const expected = condition[operator];
180
+ return expected ? compareAppByChannelWithOperator(version, operator, expected) : true;
181
+ });
182
+ }
137
183
  function matchesVersionCondition(version, condition) {
138
184
  if (!condition) {
139
185
  return true;
@@ -168,10 +214,7 @@ function resolveAppChannel(version) {
168
214
  if (channel === 'beta') {
169
215
  return 'beta';
170
216
  }
171
- if (channel === 'rc') {
172
- return 'rc';
173
- }
174
- return 'unknownPrerelease';
217
+ return 'stable';
175
218
  }
176
219
  function evaluateAppByChannelCondition(version, condition) {
177
220
  if (!condition) {
@@ -208,7 +251,7 @@ function evaluateAppByChannelCondition(version, condition) {
208
251
  const comparableVersion = normalizeAppByChannelComparableVersion(version);
209
252
  const comparableCondition = normalizeAppByChannelCondition(channelCondition);
210
253
  return {
211
- result: matchesVersionCondition(comparableVersion, comparableCondition) ? 'match' : 'mismatch',
254
+ result: matchesAppByChannelVersionCondition(comparableVersion, comparableCondition) ? 'match' : 'mismatch',
212
255
  channel,
213
256
  condition: channelCondition,
214
257
  };
@@ -19,7 +19,8 @@ export const PWC_FORM_META_STEP = '_pwcStep';
19
19
  /** Form POST JSON meta field: current field key when validating a single field. */
20
20
  export const PWC_FORM_META_FIELD = '_pwcField';
21
21
  const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
22
- const DEFAULT_HOST = '127.0.0.1';
22
+ const DEFAULT_PUBLIC_HOST = '127.0.0.1';
23
+ const LISTEN_HOST = '0.0.0.0';
23
24
  function resolveUiText(text, locale, fallback = '') {
24
25
  return resolveLocalizedText(text, { locale, fallback });
25
26
  }
@@ -705,7 +706,7 @@ function runPromptCatalogWebUIImpl(options) {
705
706
  const initialShow = reflowWebFormState(merged, Object.fromEntries(Object.entries(formDefaults).map(([k, v]) => [k, v])), userSeed).show;
706
707
  const submitPath = options.submitPath ?? DEFAULT_SUBMIT;
707
708
  const reflowPath = options.reflowPath ?? DEFAULT_REFLOW;
708
- const host = options.host ?? DEFAULT_HOST;
709
+ const publicHost = options.host ?? DEFAULT_PUBLIC_HOST;
709
710
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
710
711
  const pageTitle = resolveUiText(options.pageTitle, locale, t('promptCatalog.web.pageTitle'));
711
712
  const h1 = resolveUiText(options.documentHeading, locale, t('promptCatalog.web.documentHeading'));
@@ -753,7 +754,7 @@ function runPromptCatalogWebUIImpl(options) {
753
754
  }
754
755
  };
755
756
  const servePage = (port) => {
756
- const base = `http://${host}:${port}`;
757
+ const base = `http://${publicHost}:${port}`;
757
758
  const formInner = buildPwcFormHtml(catalog, formDefaults, initialShow, pwcStepDefs, 0, pwcNSteps, locale, uiText);
758
759
  const wizardClientJson = JSON.stringify({ n: pwcNSteps, stepDefs: pwcStepDefs });
759
760
  const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(base + resolveValidateStepPath) : 'null';
@@ -2071,11 +2072,6 @@ function runPromptCatalogWebUIImpl(options) {
2071
2072
  return page;
2072
2073
  };
2073
2074
  server = createServer((req, res) => {
2074
- if (!req.socket.remoteAddress ||
2075
- !['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.socket.remoteAddress)) {
2076
- res.writeHead(403).end();
2077
- return;
2078
- }
2079
2075
  if (req.method === 'GET' && (req.url === '/' || req.url === '')) {
2080
2076
  const addr = server?.address();
2081
2077
  const port = typeof addr === 'object' && addr ? Number(addr.port) : 0;
@@ -2212,15 +2208,15 @@ function runPromptCatalogWebUIImpl(options) {
2212
2208
  }
2213
2209
  res.writeHead(404).end();
2214
2210
  });
2215
- server.listen(options.port ?? 0, host, () => {
2211
+ server.listen(options.port ?? 0, LISTEN_HOST, () => {
2216
2212
  const addr = server?.address();
2217
2213
  if (typeof addr !== 'object' || !addr) {
2218
2214
  rejectAndClose(new Error('Failed to bind HTTP server'));
2219
2215
  return;
2220
2216
  }
2221
2217
  const port = addr.port;
2222
- const startUrl = `http://${host}:${port}/`;
2223
- options.onServerStart?.({ host, port, url: startUrl });
2218
+ const startUrl = `http://${publicHost}:${port}/`;
2219
+ options.onServerStart?.({ host: publicHost, listenHost: LISTEN_HOST, port, url: startUrl });
2224
2220
  const onOpenBrowserError = options.onOpenBrowserError ?? ((u, err) => console.warn(String(err), u));
2225
2221
  try {
2226
2222
  openUrlInDefaultBrowser(startUrl, onOpenBrowserError);
@@ -97,6 +97,9 @@ function detectChannel(currentVersion) {
97
97
  if (/-beta(?:[.-]|$)/i.test(currentVersion)) {
98
98
  return 'beta';
99
99
  }
100
+ if (/-test(?:[.-]|$)/i.test(currentVersion)) {
101
+ return 'test';
102
+ }
100
103
  return 'latest';
101
104
  }
102
105
  function readCurrentVersion(packageRoot) {
@@ -21,8 +21,8 @@ const NOCOBASE_SKILLS_NAME_PREFIX = 'nocobase-';
21
21
  // resolves and boots the package, even when the local skills installation is healthy.
22
22
  const SKILLS_LIST_TIMEOUT_MS = 15000;
23
23
  const SKILLS_NPM_VIEW_TIMEOUT_MS = 3000;
24
- const SKILLS_PACK_TIMEOUT_MS = 30000;
25
- const SKILLS_ADD_TIMEOUT_MS = 20000;
24
+ const SKILLS_PACK_TIMEOUT_MS = 120000;
25
+ const SKILLS_ADD_TIMEOUT_MS = 120000;
26
26
  const NPM_REGISTRY_UNAVAILABLE_PATTERNS = [
27
27
  'enotfound',
28
28
  'eai_again',
@@ -154,10 +154,13 @@ export async function listGlobalSkills(options = {}) {
154
154
  export async function listProjectSkills(options = {}) {
155
155
  return await listGlobalSkills(options);
156
156
  }
157
- function pickInstalledNocoBaseSkillNames(installedSkills, state) {
157
+ function pickInstalledNocoBaseSkillNames(installedSkills, state, sourceSkillNames = []) {
158
158
  const installedNames = new Set(installedSkills.map((skill) => String(skill.name ?? '').trim()).filter(Boolean));
159
- if (state?.skillNames?.length) {
160
- return state.skillNames.filter((name) => installedNames.has(name)).sort();
159
+ const managedNames = new Set([...sourceSkillNames, ...(state?.skillNames ?? [])]);
160
+ if (managedNames.size > 0) {
161
+ return Array.from(managedNames)
162
+ .filter((name) => installedNames.has(name))
163
+ .sort();
161
164
  }
162
165
  return Array.from(installedNames)
163
166
  .filter((name) => name.startsWith(NOCOBASE_SKILLS_NAME_PREFIX))
@@ -194,6 +197,31 @@ async function readCachedSkillsVersion(cacheRoot) {
194
197
  return undefined;
195
198
  }
196
199
  }
200
+ async function readCachedPackageSkillNames(globalRoot) {
201
+ const skillsDir = path.join(getCachedSkillsPackageDir(getSkillsCacheRoot(globalRoot)), 'skills');
202
+ try {
203
+ const entries = await fsp.readdir(skillsDir, { withFileTypes: true });
204
+ const skillNames = await Promise.all(entries
205
+ .filter((entry) => entry.isDirectory())
206
+ .map(async (entry) => {
207
+ const skillName = entry.name.trim();
208
+ if (!skillName) {
209
+ return undefined;
210
+ }
211
+ try {
212
+ await fsp.access(path.join(skillsDir, skillName, 'SKILL.md'));
213
+ return skillName;
214
+ }
215
+ catch {
216
+ return undefined;
217
+ }
218
+ }));
219
+ return skillNames.filter((name) => Boolean(name)).sort();
220
+ }
221
+ catch {
222
+ return [];
223
+ }
224
+ }
197
225
  async function resolvePackedSkillsTarball(packRoot) {
198
226
  const entries = await fsp.readdir(packRoot, { withFileTypes: true });
199
227
  const tarballs = entries
@@ -251,6 +279,7 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
251
279
  const cachedVersion = await readCachedSkillsVersion(cacheRoot);
252
280
  await fsp.mkdir(cacheRoot, { recursive: true });
253
281
  if (targetVersion && cachedVersion && compareVersions(cachedVersion, targetVersion) === 0) {
282
+ options.onProgress?.(`Using cached ${NOCOBASE_SKILLS_PACKAGE_NAME}@${targetVersion}...`);
254
283
  return {
255
284
  packageDir,
256
285
  cleanup: async () => undefined,
@@ -259,12 +288,14 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
259
288
  await fsp.rm(packRoot, { recursive: true, force: true });
260
289
  await fsp.mkdir(packRoot, { recursive: true });
261
290
  try {
262
- await (options.runFn ?? run)('npm', ['pack', '--silent', packageSpec], {
291
+ options.onProgress?.(`Downloading ${packageSpec}...`);
292
+ await (options.runFn ?? run)('npm', ['pack', ...(options.verbose ? [] : ['--silent']), packageSpec], {
263
293
  cwd: packRoot,
264
294
  stdio: options.verbose ? 'inherit' : 'ignore',
265
295
  errorName: 'npm pack',
266
296
  timeoutMs: SKILLS_PACK_TIMEOUT_MS,
267
297
  });
298
+ options.onProgress?.(`Extracting ${NOCOBASE_SKILLS_PACKAGE_NAME}...`);
268
299
  const tarballPath = await resolvePackedSkillsTarball(packRoot);
269
300
  await extractPackedSkillsTarball(tarballPath, cacheRoot, targetVersion);
270
301
  }
@@ -279,14 +310,16 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
279
310
  export async function inspectSkillsStatus(options = {}) {
280
311
  const globalRoot = resolveSkillsRoot(options);
281
312
  const stateFile = getManagedSkillsStateFile(globalRoot);
282
- const [installedSkills, managedState] = await Promise.all([
313
+ const [installedSkills, managedState, cachedSkillNames] = await Promise.all([
283
314
  listGlobalSkills({
284
315
  globalRoot,
285
316
  commandOutputFn: options.commandOutputFn,
286
317
  }),
287
318
  readManagedSkillsState(globalRoot),
319
+ readCachedPackageSkillNames(globalRoot),
288
320
  ]);
289
- const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
321
+ const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState, cachedSkillNames);
322
+ const packageSkillNames = cachedSkillNames;
290
323
  const managedByNb = managedState?.packageName === NOCOBASE_SKILLS_PACKAGE_NAME;
291
324
  let latestVersion;
292
325
  let registryError;
@@ -312,6 +345,7 @@ export async function inspectSkillsStatus(options = {}) {
312
345
  managedByNb,
313
346
  sourcePackage: managedState?.sourcePackage ?? NOCOBASE_SKILLS_SOURCE,
314
347
  npmPackageName: managedState?.packageName ?? NOCOBASE_SKILLS_PACKAGE_NAME,
348
+ packageSkillNames,
315
349
  installedSkillNames,
316
350
  latestVersion,
317
351
  installedVersion,
@@ -321,13 +355,18 @@ export async function inspectSkillsStatus(options = {}) {
321
355
  registryError,
322
356
  };
323
357
  }
324
- async function persistManagedSkillsState(globalRoot, options = {}) {
325
- const installedSkills = await listGlobalSkills({
326
- globalRoot,
327
- commandOutputFn: options.commandOutputFn,
328
- });
329
- const managedState = await readManagedSkillsState(globalRoot);
330
- const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
358
+ async function persistManagedSkillsState(globalRoot, options = {}, installedVersion) {
359
+ const [installedSkills, managedState, cachedSkillNames] = await Promise.all([
360
+ listGlobalSkills({
361
+ globalRoot,
362
+ commandOutputFn: options.commandOutputFn,
363
+ }),
364
+ readManagedSkillsState(globalRoot),
365
+ readCachedPackageSkillNames(globalRoot),
366
+ ]);
367
+ const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState, cachedSkillNames);
368
+ const packageSkillNames = cachedSkillNames.length ? cachedSkillNames : installedSkillNames;
369
+ const cachedVersion = await readCachedSkillsVersion(getSkillsCacheRoot(globalRoot));
331
370
  const published = await readPublishedSkillsVersion({
332
371
  globalRoot,
333
372
  commandOutputFn: options.commandOutputFn,
@@ -338,8 +377,8 @@ async function persistManagedSkillsState(globalRoot, options = {}) {
338
377
  sourcePackage: NOCOBASE_SKILLS_SOURCE,
339
378
  installedAt: managedState?.installedAt ?? now,
340
379
  updatedAt: now,
341
- installedVersion: published.version,
342
- skillNames: installedSkillNames,
380
+ installedVersion: installedVersion ?? cachedVersion ?? published.version,
381
+ skillNames: packageSkillNames,
343
382
  });
344
383
  return await inspectSkillsStatus({
345
384
  globalRoot,
@@ -349,7 +388,8 @@ async function persistManagedSkillsState(globalRoot, options = {}) {
349
388
  async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
350
389
  const prepared = await prepareLocalSkillsPackage(globalRoot, options, targetVersion);
351
390
  try {
352
- await (options.runFn ?? run)('npx', ['-y', 'skills', 'add', prepared.packageDir, '-g', '-y'], {
391
+ options.onProgress?.('Installing NocoBase AI coding skills globally...');
392
+ await (options.runFn ?? run)('npx', ['-y', 'skills', 'add', prepared.packageDir, '-g', '-y', '--skill', '*'], {
353
393
  cwd: globalRoot,
354
394
  stdio: options.verbose ? 'inherit' : 'ignore',
355
395
  errorName: 'skills add',
@@ -360,31 +400,66 @@ async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
360
400
  await prepared.cleanup();
361
401
  }
362
402
  }
403
+ function pickObsoleteManagedSkillNames(installedSkillNames, packageSkillNames) {
404
+ if (!packageSkillNames.length) {
405
+ return [];
406
+ }
407
+ const packageSkillNameSet = new Set(packageSkillNames);
408
+ return installedSkillNames.filter((skillName) => !packageSkillNameSet.has(skillName)).sort();
409
+ }
410
+ async function removeObsoleteManagedSkills(globalRoot, installedSkillNames, options = {}) {
411
+ const packageSkillNames = await readCachedPackageSkillNames(globalRoot);
412
+ const obsoleteSkillNames = pickObsoleteManagedSkillNames(installedSkillNames, packageSkillNames);
413
+ for (const skillName of obsoleteSkillNames) {
414
+ options.onProgress?.(`Removing obsolete skill ${skillName}...`);
415
+ await (options.runFn ?? run)('npx', ['-y', 'skills', 'remove', skillName, '-g', '-y'], {
416
+ cwd: globalRoot,
417
+ stdio: options.verbose ? 'inherit' : 'ignore',
418
+ errorName: 'skills remove',
419
+ });
420
+ }
421
+ }
363
422
  export async function installNocoBaseSkills(options = {}) {
364
423
  const globalRoot = resolveSkillsRoot(options);
424
+ options.onProgress?.('Checking installed NocoBase AI coding skills...');
365
425
  const status = await inspectSkillsStatus({
366
426
  globalRoot,
367
427
  commandOutputFn: options.commandOutputFn,
368
428
  });
369
- if (status.installed) {
429
+ const cachedSkillNames = await readCachedPackageSkillNames(globalRoot);
430
+ const missingCachedSkillNames = cachedSkillNames.filter((name) => !status.installedSkillNames.includes(name));
431
+ const obsoleteSkillNames = pickObsoleteManagedSkillNames(status.installedSkillNames, cachedSkillNames);
432
+ const targetVersion = String(options.targetVersion ?? '').trim() || undefined;
433
+ const targetVersionMatches = !targetVersion || status.installedVersion === targetVersion;
434
+ if (status.installed && targetVersionMatches && missingCachedSkillNames.length === 0 && obsoleteSkillNames.length === 0) {
370
435
  return {
371
436
  action: 'noop',
372
437
  status,
373
438
  };
374
439
  }
375
440
  await ensureSkillsWorkspaceRoot(globalRoot);
376
- await reinstallManagedSkills(globalRoot, options, status.latestVersion);
441
+ if (!status.installed || !targetVersionMatches || missingCachedSkillNames.length > 0) {
442
+ const installVersion = targetVersion ?? status.latestVersion;
443
+ await reinstallManagedSkills(globalRoot, options, installVersion);
444
+ }
445
+ await removeObsoleteManagedSkills(globalRoot, status.installedSkillNames, options);
446
+ options.onProgress?.('Verifying installed NocoBase AI coding skills...');
377
447
  return {
378
448
  action: 'installed',
379
- status: await persistManagedSkillsState(globalRoot, options),
449
+ status: await persistManagedSkillsState(globalRoot, options, targetVersion),
380
450
  };
381
451
  }
382
452
  export async function updateNocoBaseSkills(options = {}) {
383
453
  const globalRoot = resolveSkillsRoot(options);
454
+ options.onProgress?.('Checking installed NocoBase AI coding skills...');
384
455
  const status = await inspectSkillsStatus({
385
456
  globalRoot,
386
457
  commandOutputFn: options.commandOutputFn,
387
458
  });
459
+ const cachedSkillNames = await readCachedPackageSkillNames(globalRoot);
460
+ const missingCachedSkillNames = cachedSkillNames.filter((name) => !status.installedSkillNames.includes(name));
461
+ const obsoleteSkillNames = pickObsoleteManagedSkillNames(status.installedSkillNames, cachedSkillNames);
462
+ const targetVersion = String(options.targetVersion ?? '').trim() || undefined;
388
463
  if (!status.installed) {
389
464
  return {
390
465
  action: 'noop',
@@ -393,8 +468,11 @@ export async function updateNocoBaseSkills(options = {}) {
393
468
  };
394
469
  }
395
470
  if (status.managedByNb &&
471
+ !targetVersion &&
396
472
  status.latestVersion &&
397
473
  status.installedVersion &&
474
+ missingCachedSkillNames.length === 0 &&
475
+ obsoleteSkillNames.length === 0 &&
398
476
  compareVersions(status.latestVersion, status.installedVersion) <= 0) {
399
477
  return {
400
478
  action: 'noop',
@@ -402,10 +480,25 @@ export async function updateNocoBaseSkills(options = {}) {
402
480
  status,
403
481
  };
404
482
  }
405
- await reinstallManagedSkills(globalRoot, options, status.latestVersion);
483
+ if (targetVersion &&
484
+ status.installedVersion === targetVersion &&
485
+ missingCachedSkillNames.length === 0 &&
486
+ obsoleteSkillNames.length === 0) {
487
+ return {
488
+ action: 'noop',
489
+ reason: 'up-to-date',
490
+ status,
491
+ };
492
+ }
493
+ if (!targetVersion || status.installedVersion !== targetVersion || missingCachedSkillNames.length > 0) {
494
+ const installVersion = targetVersion ?? status.latestVersion;
495
+ await reinstallManagedSkills(globalRoot, options, installVersion);
496
+ }
497
+ await removeObsoleteManagedSkills(globalRoot, status.installedSkillNames, options);
498
+ options.onProgress?.('Verifying installed NocoBase AI coding skills...');
406
499
  return {
407
500
  action: 'updated',
408
- status: await persistManagedSkillsState(globalRoot, options),
501
+ status: await persistManagedSkillsState(globalRoot, options, targetVersion),
409
502
  };
410
503
  }
411
504
  export async function removeNocoBaseSkills(options = {}) {
@@ -1,4 +1,10 @@
1
1
  {
2
+ "entry": {
3
+ "windowsAdministratorRequired": {
4
+ "message": "NocoBase CLI must be run as Administrator on Windows.",
5
+ "hint": "Open your terminal as Administrator, then run the command again."
6
+ }
7
+ },
2
8
  "promptCatalog": {
3
9
  "common": {
4
10
  "cancelled": "Cancelled.",
@@ -399,7 +405,7 @@
399
405
  "envExists": "Env \"{{envName}}\" already exists. Choose another env name."
400
406
  },
401
407
  "messages": {
402
- "title": "Set up NocoBase for coding agents",
408
+ "title": "Set up NocoBase",
403
409
  "appNameRequiredWhenSkipped": "Env name is required when prompts are skipped.",
404
410
  "appNameEnvHelp": "Use `nb init --yes --env <envName>` to continue.",
405
411
  "resumeEnvRequired": "Env name is required when resuming setup.",
@@ -437,9 +443,9 @@
437
443
  }
438
444
  },
439
445
  "webUi": {
440
- "pageTitle": "Set up NocoBase for coding agents",
441
- "documentHeading": "Set up NocoBase for coding agents",
442
- "documentHint": "Install a new app, manage one that already exists on this machine, or connect a remote app so coding agents can access and work with NocoBase.",
446
+ "pageTitle": "Set up NocoBase",
447
+ "documentHeading": "Set up NocoBase",
448
+ "documentHint": "Install a new app, manage an existing one, or connect a remote app. You can use it directly or let a coding agent access it later.",
443
449
  "gettingStarted": {
444
450
  "title": "Getting started",
445
451
  "description": "Choose whether to install a new app, manage a local app, or connect a remote app."
@@ -1,4 +1,10 @@
1
1
  {
2
+ "entry": {
3
+ "windowsAdministratorRequired": {
4
+ "message": "Windows 上运行 NocoBase CLI 必须使用管理员模式。",
5
+ "hint": "请以管理员身份打开终端,然后重新执行命令。"
6
+ }
7
+ },
2
8
  "promptCatalog": {
3
9
  "common": {
4
10
  "cancelled": "已取消。",
@@ -399,7 +405,7 @@
399
405
  "envExists": "Env \"{{envName}}\" 已存在,请换一个 env name。"
400
406
  },
401
407
  "messages": {
402
- "title": "配置供 Coding Agents 使用的 NocoBase",
408
+ "title": "配置 NocoBase",
403
409
  "appNameRequiredWhenSkipped": "跳过 prompts 时必须提供 Env name。",
404
410
  "appNameEnvHelp": "请使用 `nb init --yes --env <envName>` 继续。",
405
411
  "resumeEnvRequired": "恢复安装时必须提供 Env name。",
@@ -437,9 +443,9 @@
437
443
  }
438
444
  },
439
445
  "webUi": {
440
- "pageTitle": "配置供 Coding Agents 使用的 NocoBase",
441
- "documentHeading": "配置供 Coding Agents 使用的 NocoBase",
442
- "documentHint": "安装一个新的应用、接管本机已有应用,或连接远程应用,让 Coding Agents 可以在当前工作区中访问和操作 NocoBase。",
446
+ "pageTitle": "配置 NocoBase",
447
+ "documentHeading": "配置 NocoBase",
448
+ "documentHint": "安装一个新的应用、管理已有应用,或连接远程应用。你可以直接使用它,也可以稍后让 coding agent 访问它。",
443
449
  "gettingStarted": {
444
450
  "title": "开始设置",
445
451
  "description": "选择新安装、本机接管,或远程连接。"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.2.0-beta.1",
3
+ "version": "2.2.0-beta.3",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -12,6 +12,11 @@
12
12
  "keywords": [],
13
13
  "author": "",
14
14
  "license": "Apache-2.0",
15
+ "files": [
16
+ "bin",
17
+ "dist",
18
+ "nocobase-ctl.config.json"
19
+ ],
15
20
  "bin": {
16
21
  "nb": "./bin/run.js"
17
22
  },
@@ -138,5 +143,5 @@
138
143
  "type": "git",
139
144
  "url": "git+https://github.com/nocobase/nocobase.git"
140
145
  },
141
- "gitHead": "f82fa9d0c3aa8e00e53dd94e404a312483b4866b"
146
+ "gitHead": "7b16bb2cfd427c110c6671252138cd85155723c5"
142
147
  }
@@ -1,23 +0,0 @@
1
- # Rendered by `nb env proxy`.
2
- # Context:
3
- # publicBasePath={{publicBasePath}}
4
- # apiBasePath={{apiBasePath}}
5
- # wsPath={{wsPath}}
6
- # v2PublicPath={{v2PublicPath}}
7
- # backendUrl={{backendUrl}}
8
- # snippetsDir={{snippetsDir}}
9
- # uploadsDir={{uploadsDir}}
10
- # distRootDir={{distRootDir}}
11
- # entryDir={{entryDir}}
12
- # publicDir={{publicDir}}
13
-
14
- server {
15
- listen 80;
16
- server_name _;
17
-
18
- # Add custom directives or locations above the managed block as needed.
19
-
20
- {{managedConfigBlock}}
21
-
22
- # Add custom directives or locations below the managed block as needed.
23
- }
@@ -1,5 +0,0 @@
1
- # Managed by `nb env proxy`. Changes will be overwritten.
2
-
3
- include {{snippetsDir}}/log-format-http.conf;
4
- include {{snippetsDir}}/maps-http.conf;
5
- include {{appConfigIncludePath}};
@@ -1,5 +0,0 @@
1
- expires 365d;
2
- add_header Cache-Control "public" always;
3
-
4
- access_log off;
5
- autoindex off;
@@ -1,17 +0,0 @@
1
- gzip on;
2
- gzip_types
3
- text/plain
4
- text/css
5
- text/xml
6
- text/markdown
7
- text/javascript
8
- application/javascript
9
- application/json
10
- application/manifest+json
11
- application/atom+xml
12
- application/rss+xml
13
- application/xml
14
- application/xml+rss
15
- application/xhtml+xml
16
- application/wasm
17
- image/svg+xml;
@@ -1,13 +0,0 @@
1
- log_format apm '"$time_local" client=$remote_addr '
2
- 'method=$request_method request="$request" '
3
- 'request_length=$request_length '
4
- 'status=$status bytes_sent=$bytes_sent '
5
- 'body_bytes_sent=$body_bytes_sent '
6
- 'referer=$http_referer '
7
- 'user_agent="$http_user_agent" '
8
- 'upstream_addr=$upstream_addr '
9
- 'upstream_status=$upstream_status '
10
- 'request_time=$request_time '
11
- 'upstream_response_time=$upstream_response_time '
12
- 'upstream_connect_time=$upstream_connect_time '
13
- 'upstream_header_time=$upstream_header_time';
@@ -1,14 +0,0 @@
1
- map $http_upgrade $connection_upgrade {
2
- default upgrade;
3
- "" close;
4
- }
5
-
6
- map $http_x_forwarded_proto $upstream_x_forwarded_proto {
7
- default $http_x_forwarded_proto;
8
- "" $scheme;
9
- }
10
-
11
- map $http_host $final_host {
12
- default $http_host;
13
- "" $host;
14
- }