neutrinos-cli 2.0.0-beta.1 → 2.0.0-beta.10

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 (78) hide show
  1. package/.configs/auth.json +1 -1
  2. package/.configs/preferences.json +1 -1
  3. package/README.md +52 -11
  4. package/dist/{src/bin → bin}/cli.js +62 -4
  5. package/dist/{src/commands → commands}/attribute.js +5 -5
  6. package/dist/{src/commands → commands}/deprecate.js +1 -1
  7. package/dist/commands/dev.js +15 -0
  8. package/dist/commands/doctor.js +69 -0
  9. package/dist/commands/migrate.js +120 -0
  10. package/dist/{src/commands → commands}/new-workspace.js +26 -20
  11. package/dist/{src/commands → commands}/publish.js +1 -1
  12. package/dist/commands/test.js +27 -0
  13. package/dist/types/session.js +1 -0
  14. package/dist/types/workspace.js +1 -0
  15. package/dist/{src/utils → utils}/attribute-utils.js +2 -6
  16. package/dist/utils/doctor-checks/auth-state.js +35 -0
  17. package/dist/utils/doctor-checks/cli-update-available.js +58 -0
  18. package/dist/utils/doctor-checks/component-entry-file.js +47 -0
  19. package/dist/utils/doctor-checks/get-packages-safe.js +16 -0
  20. package/dist/utils/doctor-checks/index.js +30 -0
  21. package/dist/utils/doctor-checks/local-cli-version.js +39 -0
  22. package/dist/utils/doctor-checks/lock-file-sync.js +59 -0
  23. package/dist/utils/doctor-checks/node-modules.js +24 -0
  24. package/dist/utils/doctor-checks/node-version.js +24 -0
  25. package/dist/utils/doctor-checks/package-alpha-block.js +76 -0
  26. package/dist/utils/doctor-checks/plugin-json.js +63 -0
  27. package/dist/utils/doctor-checks/plugins-server.js +28 -0
  28. package/dist/utils/doctor-checks/root-package-json.js +58 -0
  29. package/dist/utils/doctor-checks/storybook.js +26 -0
  30. package/dist/utils/doctor-checks/tsconfig.js +37 -0
  31. package/dist/utils/doctor-checks/vitest-config.js +16 -0
  32. package/dist/{src/utils → utils}/generate-component.js +4 -3
  33. package/dist/utils/local-cli.js +39 -0
  34. package/dist/utils/migrate-runner.js +109 -0
  35. package/dist/utils/migrations/2.0.0-beta.7.js +84 -0
  36. package/dist/utils/migrations/2.0.0-beta.9.js +219 -0
  37. package/dist/utils/migrations/index.js +14 -0
  38. package/dist/utils/migrations/version-compare.js +47 -0
  39. package/dist/{src/utils → utils}/path-utils.js +1 -0
  40. package/dist/utils/registry.js +24 -0
  41. package/dist/utils/spawn-cli.js +30 -0
  42. package/package.json +8 -9
  43. package/templates/component/.component.ts.hbs +2 -2
  44. package/templates/component/.stories.ts.hbs +59 -0
  45. package/templates/project/.storybook/decorators/event-logger.ts +153 -0
  46. package/templates/project/.storybook/main.ts +18 -0
  47. package/templates/project/.storybook/preview.ts +12 -0
  48. package/templates/project/.vscode/extensions.json +1 -4
  49. package/templates/project/tsconfig.json +3 -6
  50. package/templates/project/vitest.config.ts +22 -0
  51. package/dist/src/commands/dev.js +0 -10
  52. package/templates/component/.spec.ts.hbs +0 -15
  53. package/templates/project/index.html +0 -24
  54. package/templates/project/index.ts +0 -86
  55. /package/dist/{src/cli-auth → cli-auth}/auth.js +0 -0
  56. /package/dist/{src/cli-auth → cli-auth}/server.js +0 -0
  57. /package/dist/{src/cli-auth → cli-auth}/services/auth-utils.js +0 -0
  58. /package/dist/{src/commands → commands}/build.js +0 -0
  59. /package/dist/{src/commands → commands}/completion.js +0 -0
  60. /package/dist/{src/commands → commands}/generate.js +0 -0
  61. /package/dist/{src/commands → commands}/select-packages.js +0 -0
  62. /package/dist/{src/commands → commands}/serve.js +0 -0
  63. /package/dist/{src/types → types}/alpha-package.js +0 -0
  64. /package/dist/{src/types/marketplace.js → types/doctor.js} +0 -0
  65. /package/dist/{src/types/session.js → types/marketplace.js} +0 -0
  66. /package/dist/{src/types/workspace.js → types/migrate.js} +0 -0
  67. /package/dist/{src/utils → utils}/check-valid-ws.js +0 -0
  68. /package/dist/{src/utils → utils}/copy-utils.js +0 -0
  69. /package/dist/{src/utils → utils}/create-client.js +0 -0
  70. /package/dist/{src/utils → utils}/file-utils.js +0 -0
  71. /package/dist/{src/utils → utils}/generate-module.js +0 -0
  72. /package/dist/{src/utils → utils}/get-package-info.js +0 -0
  73. /package/dist/{src/utils → utils}/get-packages.js +0 -0
  74. /package/dist/{src/utils → utils}/inquirer-utils.js +0 -0
  75. /package/dist/{src/utils → utils}/logger.js +0 -0
  76. /package/dist/{src/utils → utils}/marketplace-api-utils.js +0 -0
  77. /package/dist/{src/utils → utils}/prettify.js +0 -0
  78. /package/dist/{src/utils → utils}/user-session-utils.js +0 -0
@@ -2,4 +2,4 @@
2
2
  "clientId": "your-client-id",
3
3
  "clientSecret": "your-client-secret",
4
4
  "redirectUri": "http://localhost:3000/callback"
5
- }
5
+ }
@@ -2,4 +2,4 @@
2
2
  "theme": "light",
3
3
  "language": "en",
4
4
  "showNotifications": true
5
- }
5
+ }
package/README.md CHANGED
@@ -30,17 +30,19 @@ neutrinos start
30
30
  neutrinos <command> [options]
31
31
  ```
32
32
 
33
- | Command | Description |
34
- |---------|-------------|
35
- | `new <name>` | Create a new plugin workspace |
36
- | `generate\|g` | Generate a component, module, or attribute |
37
- | `build <type> [name]` | Build plugins (`plugins` or `docker`) |
38
- | `start` | Start the Vite dev server |
39
- | `serve` | Start the Express plugins server |
40
- | `publish [name]` | Bundle and publish a package |
41
- | `deprecate [name]` | Deprecate a published package |
42
- | `auth` | Login or check auth state |
43
- | `completion <shell>` | Output shell completion script (`bash` or `zsh`) |
33
+ | Command | Description |
34
+ | --------------------- | ----------------------------------------------------------------- |
35
+ | `new <name>` | Create a new plugin workspace |
36
+ | `generate\|g` | Generate a component, module, or attribute |
37
+ | `build <type> [name]` | Build plugins (`plugins` or `docker`) |
38
+ | `start` | Start the Vite dev server |
39
+ | `serve` | Start the Express plugins server |
40
+ | `publish [name]` | Bundle and publish a package |
41
+ | `deprecate [name]` | Deprecate a published package |
42
+ | `auth` | Login or check auth state |
43
+ | `doctor` | Check workspace health (`--fix` to auto-repair) |
44
+ | `migrate` | Migrate workspace to current CLI version (`--dry-run` to preview) |
45
+ | `completion <shell>` | Output shell completion script (`bash` or `zsh`) |
44
46
 
45
47
  ### `new <name>`
46
48
 
@@ -106,6 +108,45 @@ neutrinos deprecate [name] [-y] [--all]
106
108
 
107
109
  Marks a published package as deprecated. Prompts for confirmation unless `-y` is passed.
108
110
 
111
+ ### `doctor`
112
+
113
+ ```sh
114
+ neutrinos doctor # Run all health checks
115
+ neutrinos doctor --fix # Auto-fix what it can
116
+ ```
117
+
118
+ Runs 14 workspace health checks and reports pass/warn/fail for each:
119
+
120
+ - `node_modules` — Dependencies installed
121
+ - `plugin.json` — Exists with required fields (`name`, `components.selectorPrefix`, `modules.idPrefix`)
122
+ - `package.json` — Has `workspaces`, `type: "module"`, expected dependencies
123
+ - `tsconfig.json` — Exists and is valid (verified via `tsc --showConfig`)
124
+ - `plugins-server/` — Directory exists with `index.js`
125
+ - `.storybook/` — Storybook config exists with `main.ts` and `preview.ts`
126
+ - `vitest.config.ts` — Vitest config exists
127
+ - Package `alpha` block — Each package has `alpha.component` or `alpha.module`
128
+ - Component entry file — Component packages have a matching `<name>.ts` file
129
+ - Node version — Meets `>=22` requirement
130
+ - Auth state — Token file exists and is not expired
131
+ - Lock file sync — Lock file is up to date with `package.json`
132
+ - CLI update available — Checks npm registry for newer versions
133
+ - Local CLI version — `devDependencies['neutrinos-cli']` matches running CLI
134
+
135
+ `--fix` auto-repairs: missing `node_modules` (runs install), `plugin.json` defaults, `workspaces`/`type` in `package.json`, `plugins-server/` from template, `.storybook/` from template, `vitest.config.ts` from template, missing `alpha` blocks, stale lock files.
136
+
137
+ ### `migrate`
138
+
139
+ ```sh
140
+ neutrinos migrate # Migrate workspace to current CLI version
141
+ neutrinos migrate --dry-run # Preview changes without writing
142
+ ```
143
+
144
+ Upgrades an existing workspace to match the current CLI version. Handles dependency updates, Storybook config scaffolding, file cleanup, and version pinning.
145
+
146
+ The migration enforces git safety: it errors on dirty working trees, auto-inits git if absent, and commits changes on success. All steps are idempotent — safe to re-run after `git checkout .`.
147
+
148
+ Version tracking uses `devDependencies['neutrinos-cli']` — no separate schema version field.
149
+
109
150
  ### `auth`
110
151
 
111
152
  ```sh
@@ -4,6 +4,29 @@ if (major < 22) {
4
4
  console.error('neutrinos requires Node.js 22+. Current: ' + process.version);
5
5
  process.exit(1);
6
6
  }
7
+ // Delegate to workspace-local CLI binary if it exists.
8
+ // Only `new` always runs from the global binary (no workspace context to delegate to).
9
+ // Everything else delegates to local, matching Angular CLI's model.
10
+ import { resolveLocalCli, readLocalCliVersion } from '../utils/local-cli.js';
11
+ import { spawnCli } from '../utils/spawn-cli.js';
12
+ import { compareVersions } from '../utils/migrations/version-compare.js';
13
+ const GLOBAL_ONLY_COMMANDS = new Set(['new']);
14
+ const command = process.argv[2] ?? '';
15
+ if (!GLOBAL_ONLY_COMMANDS.has(command)) {
16
+ const localScript = resolveLocalCli(process.cwd(), realpathSync(fileURLToPath(import.meta.url)));
17
+ if (localScript) {
18
+ const globalVersion = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf-8')).version;
19
+ const localVersion = readLocalCliVersion(process.cwd());
20
+ if (localVersion && compareVersions(globalVersion, localVersion) > 0) {
21
+ process.stderr.write(`\nWarning: global neutrinos CLI v${globalVersion} is newer than local v${localVersion}.\n` +
22
+ `Run "neutrinos migrate" to update this workspace.\n\n`);
23
+ }
24
+ const code = await spawnCli(localScript, process.argv.slice(2), {
25
+ env: { NEUTRINOS_GLOBAL_VERSION: globalVersion },
26
+ });
27
+ process.exit(code);
28
+ }
29
+ }
7
30
  import dotenv from 'dotenv';
8
31
  import { PACKAGE_ROOT } from '../utils/path-utils.js';
9
32
  dotenv.config({
@@ -26,15 +49,22 @@ import { generate } from '../commands/generate.js';
26
49
  import { createWorkspace } from '../commands/new-workspace.js';
27
50
  import { publish } from '../commands/publish.js';
28
51
  import { startPluginsServer } from '../commands/serve.js';
52
+ import { runTests } from '../commands/test.js';
29
53
  import { completion } from '../commands/completion.js';
54
+ import { doctor } from '../commands/doctor.js';
55
+ import { migrate } from '../commands/migrate.js';
30
56
  import { getPackages } from '../utils/get-packages.js';
31
57
  import { validateWorkspace } from '../utils/check-valid-ws.js';
32
58
  import { done, failed, inprogress, log } from '../utils/logger.js';
33
- import { authConfigJson } from '../utils/path-utils.js';
34
- const { version: CLI_VERSION } = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
59
+ import { authConfigJson, getCliPackagePath } from '../utils/path-utils.js';
60
+ const { version: CLI_VERSION } = JSON.parse(readFileSync(getCliPackagePath(), 'utf-8'));
35
61
  export const createProgram = () => {
36
62
  const program = new Command();
37
- program.version(CLI_VERSION, '-v, --version', 'Output the version number');
63
+ const globalVersion = env.NEUTRINOS_GLOBAL_VERSION;
64
+ const versionString = globalVersion
65
+ ? `${CLI_VERSION} (local)\n${globalVersion} (global)`
66
+ : `${CLI_VERSION} (global)`;
67
+ program.version(versionString, '-v, --version', 'Output the version number');
38
68
  program
39
69
  .command('new <name>')
40
70
  .description('Create a new project')
@@ -178,6 +208,14 @@ export const createProgram = () => {
178
208
  .action((options) => {
179
209
  servePlugin(cwd(), Number(options.port) || 6969);
180
210
  });
211
+ program
212
+ .command('test')
213
+ .alias('t')
214
+ .option('-w, --watch', 'Run in watch mode')
215
+ .description('run story-based tests')
216
+ .action((options) => {
217
+ runTests(cwd(), options);
218
+ });
181
219
  program
182
220
  .command('serve')
183
221
  .option('-p, --port <port>', 'Port number for serving the plugins', '3000')
@@ -192,6 +230,21 @@ export const createProgram = () => {
192
230
  .action((shell) => {
193
231
  completion(program, shell);
194
232
  });
233
+ program
234
+ .command('doctor')
235
+ .description('Check workspace health')
236
+ .option('--fix', 'Auto-fix issues where possible')
237
+ .action((options) => {
238
+ doctor(cwd(), options);
239
+ });
240
+ program
241
+ .command('migrate')
242
+ .description('Migrate workspace to a target CLI version')
243
+ .argument('[version]', 'Target version to migrate to')
244
+ .option('--dry-run', 'Preview changes without writing')
245
+ .action(async (version, options) => {
246
+ await migrate(cwd(), { ...options, version });
247
+ });
195
248
  program
196
249
  .command('__list-packages', { hidden: true })
197
250
  .description('List workspace package names (used by shell completion)')
@@ -209,7 +262,12 @@ export const createProgram = () => {
209
262
  });
210
263
  program.hook('preAction', async (_thisCmd, actionCmd) => {
211
264
  const cmd = actionCmd.name();
212
- if (cmd === 'new' || cmd === 'login' || cmd === 'completion' || cmd === '__list-packages') {
265
+ if (cmd === 'new' ||
266
+ cmd === 'login' ||
267
+ cmd === 'completion' ||
268
+ cmd === '__list-packages' ||
269
+ cmd === 'doctor' ||
270
+ cmd === 'migrate') {
213
271
  return;
214
272
  }
215
273
  if (!validateWorkspace(cwd())) {
@@ -65,9 +65,7 @@ const OPTIONS_QUERY = {
65
65
  const getAttributeMetadata = async (options) => {
66
66
  const attrMetadata = {
67
67
  ...options,
68
- type: options.type && options.type in ATTR_TYPES
69
- ? ATTR_TYPES[options.type]
70
- : '',
68
+ type: options.type && options.type in ATTR_TYPES ? ATTR_TYPES[options.type] : '',
71
69
  };
72
70
  if (!attrMetadata.type) {
73
71
  const result = (await inquiry({
@@ -97,9 +95,11 @@ const getAttributeMetadata = async (options) => {
97
95
  const data = {
98
96
  uiType: UI_TYPES[uiType],
99
97
  label: attrMetadata.label ?? (await inquiry(LABEL_QUERY)).label,
100
- placeholder: attrMetadata.placeholder ?? (await inquiry(PLACEHOLDER_QUERY)).placeholder,
98
+ placeholder: attrMetadata.placeholder ??
99
+ (await inquiry(PLACEHOLDER_QUERY)).placeholder,
101
100
  category: attrMetadata.category ?? (await inquiry(CATEGORY_QUERY)).category,
102
- defaultValue: attrMetadata.defaultValue ?? (await inquiry(DEFAULT_VALUE_QUERY)).defaultValue,
101
+ defaultValue: attrMetadata.defaultValue ??
102
+ (await inquiry(DEFAULT_VALUE_QUERY)).defaultValue,
103
103
  };
104
104
  Object.assign(attrMetadata, data);
105
105
  const fieldMappings = FIELD_MAPPING_QUERY[attrMetadata.uiType];
@@ -12,7 +12,7 @@ const CONFIRM_INQUIRY = (packageName) => ({
12
12
  default: false,
13
13
  });
14
14
  export const deprecate = async (pkgName, wsPath, options) => {
15
- const session = await getLoggedInUserSession();
15
+ const session = (await getLoggedInUserSession());
16
16
  const packages = await selectPackages(wsPath, options.all ?? false, pkgName);
17
17
  await Promise.all(packages.map(async (componentDirPath) => {
18
18
  try {
@@ -0,0 +1,15 @@
1
+ import { spawn } from 'node:child_process';
2
+ export const servePlugin = async (wsPath, port) => {
3
+ const child = spawn('npx', ['storybook', 'dev', '-p', String(port)], {
4
+ cwd: wsPath,
5
+ stdio: 'inherit',
6
+ });
7
+ await new Promise((resolve, reject) => {
8
+ child.on('close', (code) => {
9
+ if (code === 0)
10
+ resolve();
11
+ else
12
+ reject(new Error(`Storybook exited with code ${code}`));
13
+ });
14
+ });
15
+ };
@@ -0,0 +1,69 @@
1
+ import { bold, greenBright, red, yellowBright } from 'colorette';
2
+ import { log as _log } from 'node:console';
3
+ import { checks } from '../utils/doctor-checks/index.js';
4
+ const SYMBOLS = {
5
+ pass: greenBright('✔'),
6
+ warn: yellowBright('⚠'),
7
+ fail: red('✖'),
8
+ };
9
+ const colorize = {
10
+ pass: greenBright,
11
+ warn: yellowBright,
12
+ fail: red,
13
+ };
14
+ function printResult(result) {
15
+ const sym = SYMBOLS[result.status];
16
+ const color = colorize[result.status];
17
+ _log(` ${sym} ${color(`${result.name} — ${result.message}`)}`);
18
+ }
19
+ function printFixedResult(before, after) {
20
+ _log(` ${red('✖')} → ${greenBright('✔')} ${greenBright(`${after.name} — Fixed: ${after.message}`)}`);
21
+ }
22
+ export function doctor(wsPath, opts) {
23
+ _log('\nWorkspace Health Check');
24
+ _log('──────────────────────\n');
25
+ let results = [];
26
+ for (const check of checks) {
27
+ const checkResults = check.run(wsPath);
28
+ results.push(...checkResults);
29
+ }
30
+ for (const r of results) {
31
+ printResult(r);
32
+ }
33
+ if (opts.fix) {
34
+ const fixableFailures = results.filter((r) => r.status === 'fail' && r.fixable);
35
+ if (fixableFailures.length > 0) {
36
+ _log('');
37
+ for (const check of checks) {
38
+ if (!check.fix)
39
+ continue;
40
+ const checkResults = check.run(wsPath);
41
+ const hasFixableFailure = checkResults.some((r) => r.status === 'fail' && r.fixable);
42
+ if (!hasFixableFailure)
43
+ continue;
44
+ check.fix(wsPath);
45
+ const afterResults = check.run(wsPath);
46
+ for (let i = 0; i < checkResults.length; i++) {
47
+ const before = checkResults[i];
48
+ const after = afterResults[i];
49
+ if (before.status === 'fail' && after && after.status === 'pass') {
50
+ printFixedResult(before, after);
51
+ const idx = results.findIndex((r) => r.name === before.name && r.status === 'fail');
52
+ if (idx !== -1) {
53
+ results[idx] = after;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ const passed = results.filter((r) => r.status === 'pass').length;
61
+ const warned = results.filter((r) => r.status === 'warn').length;
62
+ const failed = results.filter((r) => r.status === 'fail').length;
63
+ _log('\n──────────────────────');
64
+ _log(bold(`Summary: ${greenBright(`${passed} passed`)}, ${yellowBright(`${warned} warnings`)}, ${red(`${failed} failed`)}`));
65
+ _log('');
66
+ if (failed > 0) {
67
+ process.exit(1);
68
+ }
69
+ }
@@ -0,0 +1,120 @@
1
+ // src/commands/migrate.ts
2
+ import { exec as execCb } from 'node:child_process';
3
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ import { greenBright } from 'colorette';
8
+ import { log as _log } from 'node:console';
9
+ import ora from 'ora';
10
+ import inquirer from 'inquirer';
11
+ import { readLocalCliVersion } from '../utils/local-cli.js';
12
+ import { compareVersions } from '../utils/migrations/version-compare.js';
13
+ import { fetchRegistryVersions, versionExists } from '../utils/registry.js';
14
+ import { spawnCli } from '../utils/spawn-cli.js';
15
+ import { runMigration } from '../utils/migrate-runner.js';
16
+ import { failed } from '../utils/logger.js';
17
+ const exec = promisify(execCb);
18
+ /**
19
+ * Migrate workspace to a target CLI version.
20
+ *
21
+ * - If NEUTRINOS_MIGRATE_SPAWNED is set, runs migrations directly (subprocess mode).
22
+ * - If a version argument is provided, temp-installs that version and spawns it.
23
+ * - If no version argument, prompts the user to select from available versions.
24
+ */
25
+ export async function migrate(wsPath, opts) {
26
+ // Subprocess mode — spawned by a parent orchestrator, just run migrations
27
+ if (process.env.NEUTRINOS_MIGRATE_SPAWNED === 'true') {
28
+ runMigration(wsPath, { dryRun: opts.dryRun });
29
+ return;
30
+ }
31
+ // Resolve target version
32
+ const versions = await fetchRegistryVersions();
33
+ let target;
34
+ if (opts.version) {
35
+ if (!versionExists(opts.version, versions)) {
36
+ failed(`Version ${opts.version} not found on the npm registry.`);
37
+ process.exit(1);
38
+ }
39
+ target = opts.version;
40
+ }
41
+ else {
42
+ target = await promptForVersion(wsPath, versions);
43
+ }
44
+ // Validate: refuse downgrades
45
+ const wsVersion = readLocalCliVersion(wsPath);
46
+ if (wsVersion && compareVersions(target, wsVersion) < 0) {
47
+ failed(`Cannot downgrade from v${wsVersion} to v${target}`);
48
+ process.exit(1);
49
+ }
50
+ // Temp install and spawn
51
+ const tmpDir = mkdtempSync(join(tmpdir(), 'neutrinos-migrate-'));
52
+ let exitCode = 1;
53
+ try {
54
+ await installToTempDir(tmpDir, target);
55
+ const cliBin = join(tmpDir, 'node_modules', 'neutrinos-cli', 'dist', 'src', 'bin', 'cli.js');
56
+ if (!existsSync(cliBin)) {
57
+ failed(`Failed to locate CLI binary in fetched package. Expected: ${cliBin}`);
58
+ process.exit(1);
59
+ }
60
+ const args = ['migrate'];
61
+ if (opts.dryRun)
62
+ args.push('--dry-run');
63
+ exitCode = await spawnCli(cliBin, args, {
64
+ cwd: wsPath,
65
+ env: { NEUTRINOS_MIGRATE_SPAWNED: 'true' },
66
+ });
67
+ }
68
+ finally {
69
+ rmSync(tmpDir, { recursive: true, force: true });
70
+ }
71
+ process.exit(exitCode);
72
+ }
73
+ async function promptForVersion(wsPath, versions) {
74
+ const wsVersion = readLocalCliVersion(wsPath);
75
+ // Filter to versions newer than workspace
76
+ let available = wsVersion
77
+ ? versions.filter((v) => compareVersions(v, wsVersion) > 0)
78
+ : versions;
79
+ if (available.length === 0) {
80
+ _log(greenBright('\nAlready up to date.\n'));
81
+ process.exit(0);
82
+ }
83
+ // Sort descending (newest first)
84
+ available = available.sort((a, b) => compareVersions(b, a));
85
+ // Group: stable vs prerelease
86
+ const stable = available.filter((v) => !v.includes('-'));
87
+ const prerelease = available.filter((v) => v.includes('-'));
88
+ const choices = [];
89
+ if (stable.length > 0) {
90
+ choices.push(new inquirer.Separator('── Stable ──'));
91
+ for (const v of stable)
92
+ choices.push({ name: v, value: v });
93
+ }
94
+ if (prerelease.length > 0) {
95
+ choices.push(new inquirer.Separator('── Pre-release ──'));
96
+ for (const v of prerelease)
97
+ choices.push({ name: v, value: v });
98
+ }
99
+ const { version } = await inquirer.prompt([
100
+ {
101
+ type: 'list',
102
+ name: 'version',
103
+ message: 'Select target version:',
104
+ choices,
105
+ },
106
+ ]);
107
+ return version;
108
+ }
109
+ async function installToTempDir(tmpDir, version) {
110
+ writeFileSync(join(tmpDir, 'package.json'), JSON.stringify({ dependencies: { 'neutrinos-cli': version } }));
111
+ const spinner = ora(`Fetching neutrinos-cli@${version}...`).start();
112
+ try {
113
+ await exec('npm install --ignore-scripts --no-package-lock', { cwd: tmpDir });
114
+ spinner.succeed(`Fetched neutrinos-cli@${version}`);
115
+ }
116
+ catch (err) {
117
+ spinner.fail(`Failed to fetch neutrinos-cli@${version}`);
118
+ throw err;
119
+ }
120
+ }
@@ -4,7 +4,7 @@ import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { join } from 'node:path';
5
5
  import { exit } from 'node:process';
6
6
  import { done, failed, inprogress } from '../utils/logger.js';
7
- import { pluginJsonPath, pluginServerTemplatesPath, templatesPath } from '../utils/path-utils.js';
7
+ import { getCliPackagePath, pluginJsonPath, pluginServerTemplatesPath, templatesPath } from '../utils/path-utils.js';
8
8
  import { prettify } from '../utils/prettify.js';
9
9
  export const createWorkspace = async (dir, name) => {
10
10
  process.on('SIGINT', () => {
@@ -74,31 +74,37 @@ const cleanUp = (dir) => {
74
74
  rmSync(dir, { recursive: true });
75
75
  done('Cleaned up workspace');
76
76
  };
77
+ export const buildWorkspacePackageJson = (base, cliVersion) => ({
78
+ ...base,
79
+ scripts: {},
80
+ devDependencies: {
81
+ 'neutrinos-cli': cliVersion,
82
+ lit: '^3.3.2',
83
+ typescript: '^6.0.2',
84
+ storybook: '^10.3.1',
85
+ '@storybook/web-components-vite': '^10.3.1',
86
+ '@storybook/addon-vitest': '^10.3.1',
87
+ vitest: '^4.0.0',
88
+ '@vitest/browser': '^4.0.0',
89
+ '@vitest/browser-playwright': '^4.0.0',
90
+ },
91
+ dependencies: {
92
+ express: '^5.2.1',
93
+ '@jatahworx/alpha-annotations-lib': '^1.0.12',
94
+ },
95
+ workspaces: ['packages/*'],
96
+ type: 'module',
97
+ });
77
98
  const initializePackages = async (dir) => {
78
- inprogress('Adding default npm scripts to workspace...');
79
- const pkgJson = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
80
- const scripts = {
81
- build: 'neutrinos build',
82
- start: 'neutrinos start',
83
- serve: 'neutrinos serve',
84
- };
85
- pkgJson.devDependencies = {
86
- lit: '^3.1.4',
87
- typescript: '^5.2.2',
88
- };
89
- pkgJson.dependencies = {
90
- express: '^4.19.2',
91
- '@jatahworx/alpha-annotations-lib': '^1.0.9',
92
- };
93
- pkgJson.workspaces = ['packages/*'];
94
- pkgJson.type = 'module';
95
- Object.assign(pkgJson.scripts, {}, scripts);
99
+ inprogress('Installing default packages in workspace...');
100
+ const { version: cliVersion } = JSON.parse(readFileSync(getCliPackagePath(), 'utf-8'));
101
+ const base = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
102
+ const pkgJson = buildWorkspacePackageJson(base, cliVersion);
96
103
  writeFileSync(join(dir, 'package.json'), await prettify(pkgJson, 'json'));
97
104
  execSync('npm install', {
98
105
  cwd: dir,
99
106
  });
100
107
  done(`Installed default packages in workspace: ${bold(dir)}`);
101
- done(`Added default npm scripts to workspace: ${bold(dir)}`);
102
108
  };
103
109
  const initializeStaticServer = (dir) => {
104
110
  cpSync(pluginServerTemplatesPath(), join(dir, 'plugins-server'), {
@@ -26,7 +26,7 @@ const IMAGES_FILE_PATH_QUERY = {
26
26
  message: 'Enter the path to images of the package :',
27
27
  };
28
28
  export const publish = async (name, wsPath, options) => {
29
- const session = await getLoggedInUserSession();
29
+ const session = (await getLoggedInUserSession());
30
30
  const { packageName, componentDirPath } = await getPackageMetadata(wsPath, name);
31
31
  await handlePackagePublish(packageName, componentDirPath, wsPath, options, session);
32
32
  done('Published Successfully');
@@ -0,0 +1,27 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { failed } from '../utils/logger.js';
5
+ export const runTests = async (wsPath, options) => {
6
+ const vitestConfig = join(wsPath, 'vitest.config.ts');
7
+ if (!existsSync(vitestConfig)) {
8
+ failed('vitest.config.ts not found in workspace. Run "neutrinos doctor --fix" or create it manually.');
9
+ process.exit(1);
10
+ }
11
+ const args = ['vitest'];
12
+ if (!options.watch) {
13
+ args.push('--run');
14
+ }
15
+ const child = spawn('npx', args, {
16
+ cwd: wsPath,
17
+ stdio: 'inherit',
18
+ });
19
+ await new Promise((resolve, reject) => {
20
+ child.on('close', (code) => {
21
+ if (code === 0)
22
+ resolve();
23
+ else
24
+ reject(new Error(`Vitest exited with code ${code}`));
25
+ });
26
+ });
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -42,9 +42,7 @@ const addEventTypeDecorator = (classDeclaration, attrMetadata) => {
42
42
  }
43
43
  renderFunction.addDecorator({
44
44
  name: 'AlphaAttribute',
45
- arguments: [
46
- (writer) => writeDecoratorArguments(writer, attrMetadata),
47
- ],
45
+ arguments: [(writer) => writeDecoratorArguments(writer, attrMetadata)],
48
46
  });
49
47
  };
50
48
  const addPropertyDecorator = (classDeclaration, fieldMappings, attrMetadata) => {
@@ -68,9 +66,7 @@ const addPropertyDecorator = (classDeclaration, fieldMappings, attrMetadata) =>
68
66
  }
69
67
  property.addDecorator({
70
68
  name: 'AlphaAttribute',
71
- arguments: [
72
- (writer) => writeDecoratorArguments(writer, attrMetadata),
73
- ],
69
+ arguments: [(writer) => writeDecoratorArguments(writer, attrMetadata)],
74
70
  });
75
71
  };
76
72
  const writeDecoratorArguments = (writer, attrMetadata) => {
@@ -0,0 +1,35 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { authConfigJson } from '../path-utils.js';
3
+ export const authStateCheck = {
4
+ name: 'Auth state',
5
+ run() {
6
+ const authPath = authConfigJson();
7
+ if (!existsSync(authPath)) {
8
+ return [
9
+ {
10
+ name: 'Auth state',
11
+ status: 'fail',
12
+ message: `${authPath} not found. Run "neutrinos auth login"`,
13
+ fixable: false,
14
+ },
15
+ ];
16
+ }
17
+ try {
18
+ const tokenSet = JSON.parse(readFileSync(authPath, 'utf-8'));
19
+ if (tokenSet.expires_at && tokenSet.expires_at < Date.now() / 1000) {
20
+ return [
21
+ {
22
+ name: 'Auth state',
23
+ status: 'warn',
24
+ message: 'Token expired. Run "neutrinos auth login"',
25
+ fixable: false,
26
+ },
27
+ ];
28
+ }
29
+ }
30
+ catch {
31
+ return [{ name: 'Auth state', status: 'warn', message: 'Could not parse auth.json', fixable: false }];
32
+ }
33
+ return [{ name: 'Auth state', status: 'pass', message: 'Token present and valid', fixable: false }];
34
+ },
35
+ };
@@ -0,0 +1,58 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { getCliPackagePath } from '../path-utils.js';
4
+ export const cliUpdateAvailableCheck = {
5
+ name: 'CLI update available',
6
+ run() {
7
+ const { version: current } = JSON.parse(readFileSync(getCliPackagePath(), 'utf-8'));
8
+ const isBeta = current.includes('-beta');
9
+ const tag = isBeta ? 'beta' : 'latest';
10
+ try {
11
+ const output = execSync('npm view neutrinos-cli dist-tags --json', {
12
+ stdio: 'pipe',
13
+ encoding: 'utf-8',
14
+ timeout: 3000,
15
+ });
16
+ const tags = JSON.parse(output);
17
+ const latest = tags[tag];
18
+ if (!latest) {
19
+ return [
20
+ {
21
+ name: 'CLI update available',
22
+ status: 'pass',
23
+ message: `v${current} (could not determine ${tag} tag)`,
24
+ fixable: false,
25
+ },
26
+ ];
27
+ }
28
+ if (latest === current) {
29
+ return [
30
+ {
31
+ name: 'CLI update available',
32
+ status: 'pass',
33
+ message: `v${current} is the latest ${tag}`,
34
+ fixable: false,
35
+ },
36
+ ];
37
+ }
38
+ return [
39
+ {
40
+ name: 'CLI update available',
41
+ status: 'warn',
42
+ message: `v${current} → v${latest} available. Run "npm i -g neutrinos-cli@${tag}"`,
43
+ fixable: false,
44
+ },
45
+ ];
46
+ }
47
+ catch {
48
+ return [
49
+ {
50
+ name: 'CLI update available',
51
+ status: 'warn',
52
+ message: `v${current} (could not reach npm registry)`,
53
+ fixable: false,
54
+ },
55
+ ];
56
+ }
57
+ },
58
+ };