neutrinos-cli 2.0.0-beta.2 → 2.0.0-beta.4
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/README.md +23 -0
- package/dist/src/bin/cli.js +34 -1
- package/dist/src/commands/dev.js +12 -7
- package/dist/src/commands/doctor.js +69 -0
- package/dist/src/commands/new-workspace.js +9 -8
- package/dist/src/commands/test.js +27 -0
- package/dist/src/types/doctor.js +1 -0
- package/dist/src/utils/doctor-checks/auth-state.js +21 -0
- package/dist/src/utils/doctor-checks/component-entry-file.js +40 -0
- package/dist/src/utils/doctor-checks/get-packages-safe.js +16 -0
- package/dist/src/utils/doctor-checks/index.js +26 -0
- package/dist/src/utils/doctor-checks/lock-file-sync.js +38 -0
- package/dist/src/utils/doctor-checks/node-modules.js +17 -0
- package/dist/src/utils/doctor-checks/node-version.js +10 -0
- package/dist/src/utils/doctor-checks/package-alpha-block.js +62 -0
- package/dist/src/utils/doctor-checks/plugin-json.js +56 -0
- package/dist/src/utils/doctor-checks/plugins-server.js +21 -0
- package/dist/src/utils/doctor-checks/root-package-json.js +51 -0
- package/dist/src/utils/doctor-checks/storybook.js +26 -0
- package/dist/src/utils/doctor-checks/tsconfig.js +25 -0
- package/dist/src/utils/doctor-checks/vitest-config.js +16 -0
- package/dist/src/utils/doctor-checks.js +397 -0
- package/dist/src/utils/generate-component.js +3 -2
- package/dist/src/utils/local-cli.js +23 -0
- package/package.json +1 -1
- package/templates/component/.stories.ts.hbs +53 -0
- package/templates/project/.storybook/decorators/event-logger.ts +153 -0
- package/templates/project/.storybook/main.ts +19 -0
- package/templates/project/.storybook/preview.ts +12 -0
- package/templates/project/tsconfig.json +1 -1
- package/templates/project/vitest.config.ts +24 -0
- package/templates/component/.spec.ts.hbs +0 -15
- package/templates/project/index.html +0 -24
- package/templates/project/index.ts +0 -86
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ neutrinos <command> [options]
|
|
|
40
40
|
| `publish [name]` | Bundle and publish a package |
|
|
41
41
|
| `deprecate [name]` | Deprecate a published package |
|
|
42
42
|
| `auth` | Login or check auth state |
|
|
43
|
+
| `doctor` | Check workspace health (`--fix` to auto-repair) |
|
|
43
44
|
| `completion <shell>` | Output shell completion script (`bash` or `zsh`) |
|
|
44
45
|
|
|
45
46
|
### `new <name>`
|
|
@@ -106,6 +107,28 @@ neutrinos deprecate [name] [-y] [--all]
|
|
|
106
107
|
|
|
107
108
|
Marks a published package as deprecated. Prompts for confirmation unless `-y` is passed.
|
|
108
109
|
|
|
110
|
+
### `doctor`
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
neutrinos doctor # Run all health checks
|
|
114
|
+
neutrinos doctor --fix # Auto-fix what it can
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Runs 10 workspace health checks and reports pass/warn/fail for each:
|
|
118
|
+
|
|
119
|
+
- `node_modules` — Dependencies installed
|
|
120
|
+
- `plugin.json` — Exists with required fields (`name`, `components.selectorPrefix`, `modules.idPrefix`)
|
|
121
|
+
- `package.json` — Has `workspaces`, `type: "module"`, expected dependencies
|
|
122
|
+
- `tsconfig.json` — Exists and is valid (verified via `tsc --showConfig`)
|
|
123
|
+
- `plugins-server/` — Directory exists with `index.js`
|
|
124
|
+
- Package `alpha` block — Each package has `alpha.component` or `alpha.module`
|
|
125
|
+
- Component entry file — Component packages have a matching `<name>.ts` file
|
|
126
|
+
- Node version — Meets `>=22` requirement
|
|
127
|
+
- Auth state — Token file exists and is not expired
|
|
128
|
+
- Lock file sync — Lock file is up to date with `package.json`
|
|
129
|
+
|
|
130
|
+
`--fix` auto-repairs: missing `node_modules` (runs install), `plugin.json` defaults, `workspaces`/`type` in `package.json`, `plugins-server/` from template, missing `alpha` blocks, stale lock files.
|
|
131
|
+
|
|
109
132
|
### `auth`
|
|
110
133
|
|
|
111
134
|
```sh
|
package/dist/src/bin/cli.js
CHANGED
|
@@ -4,6 +4,21 @@ 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 and differs from this one.
|
|
8
|
+
// This ensures the workspace's pinned CLI version is used, not the global install.
|
|
9
|
+
import { resolveLocalCli } from '../utils/local-cli.js';
|
|
10
|
+
const localScript = resolveLocalCli(process.cwd(), realpathSync(fileURLToPath(import.meta.url)));
|
|
11
|
+
if (localScript) {
|
|
12
|
+
const child = spawn(process.execPath, [localScript, ...process.argv.slice(2)], {
|
|
13
|
+
stdio: 'inherit',
|
|
14
|
+
});
|
|
15
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
16
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
17
|
+
const code = await new Promise((resolve) => {
|
|
18
|
+
child.on('close', (c) => resolve(c ?? 1));
|
|
19
|
+
});
|
|
20
|
+
process.exit(code);
|
|
21
|
+
}
|
|
7
22
|
import dotenv from 'dotenv';
|
|
8
23
|
import { PACKAGE_ROOT } from '../utils/path-utils.js';
|
|
9
24
|
dotenv.config({
|
|
@@ -14,6 +29,7 @@ import { Argument, Command, Option } from 'commander';
|
|
|
14
29
|
import EventEmitter from 'node:events';
|
|
15
30
|
import { cwd, env, exit } from 'node:process';
|
|
16
31
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
32
|
+
import { spawn } from 'node:child_process';
|
|
17
33
|
import { fileURLToPath } from 'node:url';
|
|
18
34
|
import open from 'open';
|
|
19
35
|
import { join, resolve } from 'node:path';
|
|
@@ -26,7 +42,9 @@ import { generate } from '../commands/generate.js';
|
|
|
26
42
|
import { createWorkspace } from '../commands/new-workspace.js';
|
|
27
43
|
import { publish } from '../commands/publish.js';
|
|
28
44
|
import { startPluginsServer } from '../commands/serve.js';
|
|
45
|
+
import { runTests } from '../commands/test.js';
|
|
29
46
|
import { completion } from '../commands/completion.js';
|
|
47
|
+
import { doctor } from '../commands/doctor.js';
|
|
30
48
|
import { getPackages } from '../utils/get-packages.js';
|
|
31
49
|
import { validateWorkspace } from '../utils/check-valid-ws.js';
|
|
32
50
|
import { done, failed, inprogress, log } from '../utils/logger.js';
|
|
@@ -178,6 +196,14 @@ export const createProgram = () => {
|
|
|
178
196
|
.action((options) => {
|
|
179
197
|
servePlugin(cwd(), Number(options.port) || 6969);
|
|
180
198
|
});
|
|
199
|
+
program
|
|
200
|
+
.command('test')
|
|
201
|
+
.alias('t')
|
|
202
|
+
.option('-w, --watch', 'Run in watch mode')
|
|
203
|
+
.description('run story-based tests')
|
|
204
|
+
.action((options) => {
|
|
205
|
+
runTests(cwd(), options);
|
|
206
|
+
});
|
|
181
207
|
program
|
|
182
208
|
.command('serve')
|
|
183
209
|
.option('-p, --port <port>', 'Port number for serving the plugins', '3000')
|
|
@@ -192,6 +218,13 @@ export const createProgram = () => {
|
|
|
192
218
|
.action((shell) => {
|
|
193
219
|
completion(program, shell);
|
|
194
220
|
});
|
|
221
|
+
program
|
|
222
|
+
.command('doctor')
|
|
223
|
+
.description('Check workspace health')
|
|
224
|
+
.option('--fix', 'Auto-fix issues where possible')
|
|
225
|
+
.action((options) => {
|
|
226
|
+
doctor(cwd(), options);
|
|
227
|
+
});
|
|
195
228
|
program
|
|
196
229
|
.command('__list-packages', { hidden: true })
|
|
197
230
|
.description('List workspace package names (used by shell completion)')
|
|
@@ -209,7 +242,7 @@ export const createProgram = () => {
|
|
|
209
242
|
});
|
|
210
243
|
program.hook('preAction', async (_thisCmd, actionCmd) => {
|
|
211
244
|
const cmd = actionCmd.name();
|
|
212
|
-
if (cmd === 'new' || cmd === 'login' || cmd === 'completion' || cmd === '__list-packages') {
|
|
245
|
+
if (cmd === 'new' || cmd === 'login' || cmd === 'completion' || cmd === '__list-packages' || cmd === 'doctor') {
|
|
213
246
|
return;
|
|
214
247
|
}
|
|
215
248
|
if (!validateWorkspace(cwd())) {
|
package/dist/src/commands/dev.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
export const servePlugin = async (wsPath, port) => {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
});
|
|
6
14
|
});
|
|
7
|
-
await server.listen();
|
|
8
|
-
server.printUrls();
|
|
9
|
-
server.bindCLIShortcuts({ print: true });
|
|
10
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
|
+
}
|
|
@@ -75,16 +75,19 @@ const cleanUp = (dir) => {
|
|
|
75
75
|
done('Cleaned up workspace');
|
|
76
76
|
};
|
|
77
77
|
const initializePackages = async (dir) => {
|
|
78
|
-
inprogress('
|
|
78
|
+
inprogress('Installing default packages in workspace...');
|
|
79
79
|
const pkgJson = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
|
|
80
|
-
|
|
81
|
-
build: 'neutrinos build',
|
|
82
|
-
start: 'neutrinos start',
|
|
83
|
-
serve: 'neutrinos serve',
|
|
84
|
-
};
|
|
80
|
+
pkgJson.scripts = {};
|
|
85
81
|
pkgJson.devDependencies = {
|
|
82
|
+
'neutrinos-cli': '^2.0.0',
|
|
86
83
|
lit: '^3.3.2',
|
|
87
84
|
typescript: '^5.9.3',
|
|
85
|
+
storybook: '^10.3.1',
|
|
86
|
+
'@storybook/web-components-vite': '^10.3.1',
|
|
87
|
+
'@storybook/addon-vitest': '^10.3.1',
|
|
88
|
+
vitest: '^4.0.0',
|
|
89
|
+
'@vitest/browser': '^4.0.0',
|
|
90
|
+
'@vitest/browser-playwright': '^4.0.0',
|
|
88
91
|
};
|
|
89
92
|
pkgJson.dependencies = {
|
|
90
93
|
express: '^5.2.1',
|
|
@@ -92,13 +95,11 @@ const initializePackages = async (dir) => {
|
|
|
92
95
|
};
|
|
93
96
|
pkgJson.workspaces = ['packages/*'];
|
|
94
97
|
pkgJson.type = 'module';
|
|
95
|
-
Object.assign(pkgJson.scripts, {}, scripts);
|
|
96
98
|
writeFileSync(join(dir, 'package.json'), await prettify(pkgJson, 'json'));
|
|
97
99
|
execSync('npm install', {
|
|
98
100
|
cwd: dir,
|
|
99
101
|
});
|
|
100
102
|
done(`Installed default packages in workspace: ${bold(dir)}`);
|
|
101
|
-
done(`Added default npm scripts to workspace: ${bold(dir)}`);
|
|
102
103
|
};
|
|
103
104
|
const initializeStaticServer = (dir) => {
|
|
104
105
|
cpSync(pluginServerTemplatesPath(), join(dir, 'plugins-server'), {
|
|
@@ -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,21 @@
|
|
|
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 [{ name: 'Auth state', status: 'fail', message: `${authPath} not found. Run "neutrinos auth login"`, fixable: false }];
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const tokenSet = JSON.parse(readFileSync(authPath, 'utf-8'));
|
|
12
|
+
if (tokenSet.expires_at && tokenSet.expires_at < Date.now() / 1000) {
|
|
13
|
+
return [{ name: 'Auth state', status: 'warn', message: 'Token expired. Run "neutrinos auth login"', fixable: false }];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [{ name: 'Auth state', status: 'warn', message: 'Could not parse auth.json', fixable: false }];
|
|
18
|
+
}
|
|
19
|
+
return [{ name: 'Auth state', status: 'pass', message: 'Token present and valid', fixable: false }];
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { getGeneratedComponentName } from '../path-utils.js';
|
|
4
|
+
import { getPackagesSafe } from './get-packages-safe.js';
|
|
5
|
+
export const componentEntryFileCheck = {
|
|
6
|
+
name: 'Component entry file',
|
|
7
|
+
run(wsPath) {
|
|
8
|
+
const packages = getPackagesSafe(wsPath);
|
|
9
|
+
if (packages === null) {
|
|
10
|
+
return [{ name: 'Component entry file', status: 'fail', message: 'Could not discover packages', fixable: false }];
|
|
11
|
+
}
|
|
12
|
+
const results = [];
|
|
13
|
+
for (const pkgPath of packages) {
|
|
14
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
15
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
16
|
+
if (!pkgJson.alpha?.component)
|
|
17
|
+
continue;
|
|
18
|
+
const pkgName = pkgJson.name || basename(pkgPath);
|
|
19
|
+
const entryName = getGeneratedComponentName(basename(pkgPath));
|
|
20
|
+
const entryFile = join(pkgPath, `${entryName}.ts`);
|
|
21
|
+
if (!existsSync(entryFile)) {
|
|
22
|
+
results.push({
|
|
23
|
+
name: `Package "${pkgName}" entry file`,
|
|
24
|
+
status: 'fail',
|
|
25
|
+
message: `${entryName}.ts is missing`,
|
|
26
|
+
fixable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
results.push({
|
|
31
|
+
name: `Package "${pkgName}" entry file`,
|
|
32
|
+
status: 'pass',
|
|
33
|
+
message: `${entryName}.ts exists`,
|
|
34
|
+
fixable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, globSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export function getPackagesSafe(wsPath) {
|
|
4
|
+
try {
|
|
5
|
+
const packageJsonContent = readFileSync(join(wsPath, 'package.json'), 'utf-8');
|
|
6
|
+
const parsed = JSON.parse(packageJsonContent);
|
|
7
|
+
const packagesDir = parsed.workspaces ?? [];
|
|
8
|
+
const packageNames = globSync(packagesDir, { cwd: wsPath });
|
|
9
|
+
return packageNames
|
|
10
|
+
.map((name) => join(wsPath, name))
|
|
11
|
+
.filter((p) => existsSync(join(p, 'package.json')));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { nodeModulesCheck } from './node-modules.js';
|
|
2
|
+
import { pluginJsonCheck } from './plugin-json.js';
|
|
3
|
+
import { rootPackageJsonCheck } from './root-package-json.js';
|
|
4
|
+
import { tsconfigCheck } from './tsconfig.js';
|
|
5
|
+
import { pluginsServerCheck } from './plugins-server.js';
|
|
6
|
+
import { storybookCheck } from './storybook.js';
|
|
7
|
+
import { vitestConfigCheck } from './vitest-config.js';
|
|
8
|
+
import { packageAlphaBlockCheck } from './package-alpha-block.js';
|
|
9
|
+
import { componentEntryFileCheck } from './component-entry-file.js';
|
|
10
|
+
import { nodeVersionCheck } from './node-version.js';
|
|
11
|
+
import { authStateCheck } from './auth-state.js';
|
|
12
|
+
import { lockFileSyncCheck } from './lock-file-sync.js';
|
|
13
|
+
export const checks = [
|
|
14
|
+
nodeModulesCheck,
|
|
15
|
+
pluginJsonCheck,
|
|
16
|
+
rootPackageJsonCheck,
|
|
17
|
+
tsconfigCheck,
|
|
18
|
+
pluginsServerCheck,
|
|
19
|
+
storybookCheck,
|
|
20
|
+
vitestConfigCheck,
|
|
21
|
+
packageAlphaBlockCheck,
|
|
22
|
+
componentEntryFileCheck,
|
|
23
|
+
nodeVersionCheck,
|
|
24
|
+
authStateCheck,
|
|
25
|
+
lockFileSyncCheck,
|
|
26
|
+
];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const lockFileSyncCheck = {
|
|
5
|
+
name: 'Lock file sync',
|
|
6
|
+
run(wsPath) {
|
|
7
|
+
const packageJsonPath = join(wsPath, 'package.json');
|
|
8
|
+
if (!existsSync(packageJsonPath)) {
|
|
9
|
+
return [{ name: 'Lock file sync', status: 'fail', message: 'No package.json to compare against', fixable: false }];
|
|
10
|
+
}
|
|
11
|
+
const pnpmLock = join(wsPath, 'pnpm-lock.yaml');
|
|
12
|
+
const npmLock = join(wsPath, 'package-lock.json');
|
|
13
|
+
let lockPath = null;
|
|
14
|
+
let manager = 'npm';
|
|
15
|
+
if (existsSync(pnpmLock)) {
|
|
16
|
+
lockPath = pnpmLock;
|
|
17
|
+
manager = 'pnpm';
|
|
18
|
+
}
|
|
19
|
+
else if (existsSync(npmLock)) {
|
|
20
|
+
lockPath = npmLock;
|
|
21
|
+
manager = 'npm';
|
|
22
|
+
}
|
|
23
|
+
if (!lockPath) {
|
|
24
|
+
return [{ name: 'Lock file sync', status: 'fail', message: 'No lock file found. Run "npm install" or "pnpm install"', fixable: true }];
|
|
25
|
+
}
|
|
26
|
+
const lockMtime = statSync(lockPath).mtimeMs;
|
|
27
|
+
const pkgMtime = statSync(packageJsonPath).mtimeMs;
|
|
28
|
+
if (pkgMtime > lockMtime) {
|
|
29
|
+
return [{ name: 'Lock file sync', status: 'warn', message: `package.json is newer than lock file. Run "${manager} install"`, fixable: true }];
|
|
30
|
+
}
|
|
31
|
+
return [{ name: 'Lock file sync', status: 'pass', message: 'Lock file is up to date', fixable: false }];
|
|
32
|
+
},
|
|
33
|
+
fix(wsPath) {
|
|
34
|
+
const pnpmLock = join(wsPath, 'pnpm-lock.yaml');
|
|
35
|
+
const manager = existsSync(pnpmLock) ? 'pnpm' : 'npm';
|
|
36
|
+
execSync(`${manager} install`, { cwd: wsPath, stdio: 'inherit' });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const nodeModulesCheck = {
|
|
5
|
+
name: 'node_modules',
|
|
6
|
+
run(wsPath) {
|
|
7
|
+
if (!existsSync(join(wsPath, 'node_modules'))) {
|
|
8
|
+
return [{ name: 'node_modules', status: 'fail', message: 'Not installed. Run "npm install" or "pnpm install"', fixable: true }];
|
|
9
|
+
}
|
|
10
|
+
return [{ name: 'node_modules', status: 'pass', message: 'Dependencies installed', fixable: false }];
|
|
11
|
+
},
|
|
12
|
+
fix(wsPath) {
|
|
13
|
+
const pnpmLock = join(wsPath, 'pnpm-lock.yaml');
|
|
14
|
+
const manager = existsSync(pnpmLock) ? 'pnpm' : 'npm';
|
|
15
|
+
execSync(`${manager} install`, { cwd: wsPath, stdio: 'inherit' });
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const nodeVersionCheck = {
|
|
2
|
+
name: 'Node version',
|
|
3
|
+
run() {
|
|
4
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
5
|
+
if (major < 22) {
|
|
6
|
+
return [{ name: 'Node version', status: 'fail', message: `v${process.versions.node} does not meet requirement (>=22). Install Node.js 22+`, fixable: false }];
|
|
7
|
+
}
|
|
8
|
+
return [{ name: 'Node version', status: 'pass', message: `v${process.versions.node} meets requirement (>=22)`, fixable: false }];
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { getPackagesSafe } from './get-packages-safe.js';
|
|
4
|
+
export const packageAlphaBlockCheck = {
|
|
5
|
+
name: 'Package alpha block',
|
|
6
|
+
run(wsPath) {
|
|
7
|
+
const packages = getPackagesSafe(wsPath);
|
|
8
|
+
if (packages === null) {
|
|
9
|
+
return [{ name: 'Package alpha block', status: 'fail', message: 'Could not discover packages (missing package.json or workspaces)', fixable: false }];
|
|
10
|
+
}
|
|
11
|
+
if (packages.length === 0) {
|
|
12
|
+
return [{ name: 'Package alpha block', status: 'pass', message: 'No packages found (nothing to check)', fixable: false }];
|
|
13
|
+
}
|
|
14
|
+
const results = [];
|
|
15
|
+
for (const pkgPath of packages) {
|
|
16
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
17
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
18
|
+
const pkgName = pkgJson.name || basename(pkgPath);
|
|
19
|
+
if (!pkgJson.alpha) {
|
|
20
|
+
results.push({
|
|
21
|
+
name: `Package "${pkgName}" alpha block`,
|
|
22
|
+
status: 'fail',
|
|
23
|
+
message: 'No alpha block found. Add alpha.component or alpha.module',
|
|
24
|
+
fixable: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else if (!pkgJson.alpha.component && !pkgJson.alpha.module) {
|
|
28
|
+
results.push({
|
|
29
|
+
name: `Package "${pkgName}" alpha block`,
|
|
30
|
+
status: 'warn',
|
|
31
|
+
message: 'alpha block exists but neither component nor module is true',
|
|
32
|
+
fixable: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const type = pkgJson.alpha.component ? 'alpha.component' : 'alpha.module';
|
|
37
|
+
results.push({
|
|
38
|
+
name: `Package "${pkgName}" alpha block`,
|
|
39
|
+
status: 'pass',
|
|
40
|
+
message: `${type} is set`,
|
|
41
|
+
fixable: false,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return results;
|
|
46
|
+
},
|
|
47
|
+
fix(wsPath) {
|
|
48
|
+
const packages = getPackagesSafe(wsPath);
|
|
49
|
+
if (!packages)
|
|
50
|
+
return;
|
|
51
|
+
for (const pkgPath of packages) {
|
|
52
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
53
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
54
|
+
const alpha = (pkgJson['alpha'] ?? {});
|
|
55
|
+
if (!alpha['component'] && !alpha['module']) {
|
|
56
|
+
alpha['component'] = true;
|
|
57
|
+
}
|
|
58
|
+
pkgJson['alpha'] = alpha;
|
|
59
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4) + '\n');
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { pluginJsonPath } from '../path-utils.js';
|
|
4
|
+
export const pluginJsonCheck = {
|
|
5
|
+
name: 'plugin.json',
|
|
6
|
+
run(wsPath) {
|
|
7
|
+
const path = pluginJsonPath(wsPath);
|
|
8
|
+
if (!existsSync(path)) {
|
|
9
|
+
return [{ name: 'plugin.json', status: 'fail', message: 'File is missing', fixable: true }];
|
|
10
|
+
}
|
|
11
|
+
let json;
|
|
12
|
+
try {
|
|
13
|
+
json = JSON.parse(readFileSync(path, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return [{ name: 'plugin.json', status: 'fail', message: 'File is not valid JSON', fixable: false }];
|
|
17
|
+
}
|
|
18
|
+
const missing = [];
|
|
19
|
+
if (!json.name)
|
|
20
|
+
missing.push('name');
|
|
21
|
+
if (!json.components?.selectorPrefix)
|
|
22
|
+
missing.push('components.selectorPrefix');
|
|
23
|
+
if (!json.modules?.idPrefix)
|
|
24
|
+
missing.push('modules.idPrefix');
|
|
25
|
+
if (missing.length > 0) {
|
|
26
|
+
return [{ name: 'plugin.json', status: 'fail', message: `Missing required fields: ${missing.join(', ')}`, fixable: true }];
|
|
27
|
+
}
|
|
28
|
+
return [{ name: 'plugin.json', status: 'pass', message: 'All required fields present', fixable: false }];
|
|
29
|
+
},
|
|
30
|
+
fix(wsPath) {
|
|
31
|
+
const path = pluginJsonPath(wsPath);
|
|
32
|
+
const defaults = {
|
|
33
|
+
name: basename(wsPath),
|
|
34
|
+
components: { selectorPrefix: 'comp' },
|
|
35
|
+
modules: { idPrefix: 'mod' },
|
|
36
|
+
};
|
|
37
|
+
if (!existsSync(path)) {
|
|
38
|
+
writeFileSync(path, JSON.stringify(defaults, null, 4) + '\n');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let existing = {};
|
|
42
|
+
try {
|
|
43
|
+
existing = JSON.parse(readFileSync(path, 'utf-8'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
writeFileSync(path, JSON.stringify(defaults, null, 4) + '\n');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
existing.name ??= defaults.name;
|
|
50
|
+
existing.components ??= {};
|
|
51
|
+
existing.components.selectorPrefix ??= defaults.components.selectorPrefix;
|
|
52
|
+
existing.modules ??= {};
|
|
53
|
+
existing.modules.idPrefix ??= defaults.modules.idPrefix;
|
|
54
|
+
writeFileSync(path, JSON.stringify(existing, null, 4) + '\n');
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cpSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pluginServerRoot, pluginServerTemplatesPath } from '../path-utils.js';
|
|
4
|
+
export const pluginsServerCheck = {
|
|
5
|
+
name: 'plugins-server/',
|
|
6
|
+
run(wsPath) {
|
|
7
|
+
const serverDir = pluginServerRoot(wsPath);
|
|
8
|
+
if (!existsSync(serverDir)) {
|
|
9
|
+
return [{ name: 'plugins-server/', status: 'fail', message: 'Directory is missing', fixable: true }];
|
|
10
|
+
}
|
|
11
|
+
if (!existsSync(join(serverDir, 'index.js'))) {
|
|
12
|
+
return [{ name: 'plugins-server/', status: 'fail', message: 'Directory exists but index.js is missing', fixable: true }];
|
|
13
|
+
}
|
|
14
|
+
return [{ name: 'plugins-server/', status: 'pass', message: 'Directory exists with index.js', fixable: false }];
|
|
15
|
+
},
|
|
16
|
+
fix(wsPath) {
|
|
17
|
+
const serverDir = pluginServerRoot(wsPath);
|
|
18
|
+
const templateDir = pluginServerTemplatesPath();
|
|
19
|
+
cpSync(templateDir, serverDir, { recursive: true });
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const rootPackageJsonCheck = {
|
|
4
|
+
name: 'Root package.json',
|
|
5
|
+
run(wsPath) {
|
|
6
|
+
const path = join(wsPath, 'package.json');
|
|
7
|
+
if (!existsSync(path)) {
|
|
8
|
+
return [{ name: 'Root package.json', status: 'fail', message: 'File is missing', fixable: false }];
|
|
9
|
+
}
|
|
10
|
+
let json;
|
|
11
|
+
try {
|
|
12
|
+
json = JSON.parse(readFileSync(path, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [{ name: 'Root package.json', status: 'fail', message: 'File is not valid JSON', fixable: false }];
|
|
16
|
+
}
|
|
17
|
+
const issues = [];
|
|
18
|
+
if (!json['workspaces'])
|
|
19
|
+
issues.push('workspaces');
|
|
20
|
+
if (json['type'] !== 'module')
|
|
21
|
+
issues.push('type: "module"');
|
|
22
|
+
const deps = (json['dependencies'] ?? {});
|
|
23
|
+
if (!deps['@jatahworx/alpha-annotations-lib'])
|
|
24
|
+
issues.push('dependency @jatahworx/alpha-annotations-lib');
|
|
25
|
+
const devDeps = (json['devDependencies'] ?? {});
|
|
26
|
+
if (!devDeps['lit'])
|
|
27
|
+
issues.push('devDependency lit');
|
|
28
|
+
if (!devDeps['typescript'])
|
|
29
|
+
issues.push('devDependency typescript');
|
|
30
|
+
if (issues.length > 0) {
|
|
31
|
+
const fixable = issues.includes('workspaces') || issues.includes('type: "module"');
|
|
32
|
+
return [{ name: 'Root package.json', status: 'fail', message: `Missing: ${issues.join(', ')}`, fixable }];
|
|
33
|
+
}
|
|
34
|
+
return [{ name: 'Root package.json', status: 'pass', message: 'workspaces, dependencies, and type configured', fixable: false }];
|
|
35
|
+
},
|
|
36
|
+
fix(wsPath) {
|
|
37
|
+
const path = join(wsPath, 'package.json');
|
|
38
|
+
if (!existsSync(path))
|
|
39
|
+
return;
|
|
40
|
+
let json;
|
|
41
|
+
try {
|
|
42
|
+
json = JSON.parse(readFileSync(path, 'utf-8'));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
json['workspaces'] ??= ['packages/*'];
|
|
48
|
+
json['type'] ??= 'module';
|
|
49
|
+
writeFileSync(path, JSON.stringify(json, null, 4) + '\n');
|
|
50
|
+
},
|
|
51
|
+
};
|