slicejs-cli 3.4.0 → 3.5.0
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/AGENTS.md +247 -0
- package/client.js +63 -64
- package/commands/Print.js +11 -15
- package/commands/Validations.js +12 -23
- package/commands/buildProduction/buildProduction.js +23 -26
- package/commands/bundle/bundle.js +10 -11
- package/commands/createComponent/createComponent.js +14 -16
- package/commands/deleteComponent/deleteComponent.js +6 -6
- package/commands/doctor/doctor.js +11 -14
- package/commands/getComponent/getComponent.js +99 -162
- package/commands/init/init.js +77 -26
- package/commands/listComponents/listComponents.js +18 -21
- package/commands/startServer/startServer.js +21 -24
- package/commands/startServer/watchServer.js +7 -7
- package/commands/types/types.js +53 -18
- package/commands/utils/PathHelper.js +9 -2
- package/commands/utils/VersionChecker.js +3 -3
- package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
- package/commands/utils/loadConfig.js +31 -0
- package/commands/utils/updateManager.js +3 -4
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
- package/package.json +14 -2
- package/post.js +2 -2
- package/tests/bundle-generator.test.js +3 -20
- package/tests/component-registry-parse.test.js +34 -0
- package/tests/fixtures/components.js +8 -0
- package/tests/fixtures/sliceConfig.json +74 -0
- package/tests/getcomponent.test.js +407 -0
- package/tests/helpers/setup.js +97 -0
- package/tests/init-command-contract.test.js +46 -0
- package/tests/local-cli-delegation.test.js +7 -5
- package/tests/path-helper.test.js +206 -0
- package/tests/types-breakage.test.js +491 -0
- package/tests/types-generator-errors.test.js +361 -0
- package/tests/types-generator.test.js +172 -184
package/AGENTS.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Slice.js CLI — Agent Context
|
|
2
|
+
|
|
3
|
+
## Project Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
slicejs-cli/
|
|
7
|
+
├── client.js # CLI entry point (commander)
|
|
8
|
+
├── commands/
|
|
9
|
+
│ ├── init/init.js # slice init
|
|
10
|
+
│ ├── startServer/startServer.js # slice dev / slice start
|
|
11
|
+
│ ├── build/build.js # slice build
|
|
12
|
+
│ ├── getComponent/getComponent.js # slice get / browse / sync
|
|
13
|
+
│ ├── createComponent/ # slice component create
|
|
14
|
+
│ ├── listComponents/ # slice component list
|
|
15
|
+
│ ├── deleteComponent/ # slice component delete
|
|
16
|
+
│ ├── doctor/doctor.js # slice doctor
|
|
17
|
+
│ ├── types/types.js # slice types generate
|
|
18
|
+
│ ├── bundle/bundle.js # bundling logic
|
|
19
|
+
│ ├── utils/
|
|
20
|
+
│ │ ├── PathHelper.js # Path resolution (critical)
|
|
21
|
+
│ │ ├── bundling/BundleGenerator.js
|
|
22
|
+
│ │ ├── updateManager.js
|
|
23
|
+
│ │ ├── VersionChecker.js
|
|
24
|
+
│ │ └── LocalCliDelegation.js
|
|
25
|
+
│ └── Print.js # Wrapper for console.log/error
|
|
26
|
+
├── tests/
|
|
27
|
+
│ ├── helpers/setup.js # Shared test helper (createTestProject, withTestProject)
|
|
28
|
+
│ ├── fixtures/ # Minimal fixture files for tests
|
|
29
|
+
│ ├── bundle-generator.test.js
|
|
30
|
+
│ ├── bundle-v2-register-output.test.js
|
|
31
|
+
│ ├── client-launcher-contract.test.js
|
|
32
|
+
│ ├── client-update-flow-contract.test.js
|
|
33
|
+
│ ├── component-registry-parse.test.js
|
|
34
|
+
│ ├── dependency-analyzer.test.js
|
|
35
|
+
│ ├── init-command-contract.test.js
|
|
36
|
+
│ ├── local-cli-delegation.test.js
|
|
37
|
+
│ ├── path-helper.test.js
|
|
38
|
+
│ ├── postinstall-command.test.js
|
|
39
|
+
│ ├── types-generator.test.js
|
|
40
|
+
│ └── update-manager-notifications.test.js
|
|
41
|
+
├── package.json # type: "module" — ES modules only
|
|
42
|
+
└── AGENTS.md # This file
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Testing System
|
|
46
|
+
|
|
47
|
+
### Runner
|
|
48
|
+
- Uses Node.js built-in test runner: `node --test`
|
|
49
|
+
- Run: `npm test`
|
|
50
|
+
- Watch mode: `node --test --watch`
|
|
51
|
+
|
|
52
|
+
### Shared Test Helper (`tests/helpers/setup.js`)
|
|
53
|
+
Three exported functions:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { createTestProject, cleanupTestProject, withTestProject } from './helpers/setup.js';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`createTestProject(options)`** — Creates a temp directory with full Slice.js project scaffold.
|
|
60
|
+
- Copies real framework files from `../slice.js/` (sibling directory in monorepo)
|
|
61
|
+
- Falls back to `tests/fixtures/` minimal scaffold if framework not available
|
|
62
|
+
- Options:
|
|
63
|
+
- `visualComponents: ['Button']` — creates stub component files + rewrites `components.js` to include only those
|
|
64
|
+
- `frameworkDir` — custom framework source path
|
|
65
|
+
- Returns the temp directory path
|
|
66
|
+
- Temp dir path: `{os.tmpdir()}/slice-test-{PID}-{N}-{random}/`
|
|
67
|
+
|
|
68
|
+
**`cleanupTestProject(dir)`** — Removes the temp directory recursively.
|
|
69
|
+
|
|
70
|
+
**`withTestProject(fn, options)`** — Convenience wrapper that:
|
|
71
|
+
1. Calls `createTestProject(options)`
|
|
72
|
+
2. Saves and sets `process.env.INIT_CWD = dir`
|
|
73
|
+
3. Runs `fn(dir)`
|
|
74
|
+
4. Restores `process.env.INIT_CWD` to original value
|
|
75
|
+
5. Calls `cleanupTestProject(dir)` in `finally`
|
|
76
|
+
|
|
77
|
+
### Patterns
|
|
78
|
+
|
|
79
|
+
**For tests that need INIT_CWD pointing to the project:**
|
|
80
|
+
```js
|
|
81
|
+
test('my test', async () => {
|
|
82
|
+
await withTestProject(async (tmpDir) => {
|
|
83
|
+
// process.env.INIT_CWD is already set to tmpDir
|
|
84
|
+
const result = someFunction(import.meta.url);
|
|
85
|
+
assert.ok(result);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**For tests that pass projectRoot explicitly:**
|
|
91
|
+
```js
|
|
92
|
+
test('my test', async () => {
|
|
93
|
+
const tmpRoot = await createTestProject({ visualComponents: ['Button'] });
|
|
94
|
+
try {
|
|
95
|
+
const result = await someFunction({ projectRoot: tmpRoot });
|
|
96
|
+
assert.equal(result, 1);
|
|
97
|
+
} finally {
|
|
98
|
+
await cleanupTestProject(tmpRoot);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**For tests with shared project setup across a describe block:**
|
|
104
|
+
```js
|
|
105
|
+
let tmpRoot;
|
|
106
|
+
before(async () => {
|
|
107
|
+
tmpRoot = await createTestProject();
|
|
108
|
+
process.env.INIT_CWD = tmpRoot;
|
|
109
|
+
});
|
|
110
|
+
after(async () => {
|
|
111
|
+
delete process.env.INIT_CWD;
|
|
112
|
+
await cleanupTestProject(tmpRoot);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Test types
|
|
117
|
+
|
|
118
|
+
1. **Contract tests** (`*-contract.test.js`) — Static analysis of `client.js` source code via `@babel/parser` + AST or regex. Verify command registration, option flags, and function calls. No runtime execution.
|
|
119
|
+
2. **Unit tests** — Test individual functions/modules in isolation. Use temp dirs for filesystem-dependent code.
|
|
120
|
+
3. **Snapshot/Integration tests** — Verify output files, generated declarations, bundle configs.
|
|
121
|
+
|
|
122
|
+
### Rules
|
|
123
|
+
- No external mocking libraries (sinon, jest, etc.). Use monkey-patching + try/finally restore.
|
|
124
|
+
- All temp dirs MUST be cleaned up in `finally` blocks.
|
|
125
|
+
- `process.env.INIT_CWD` must be saved before modification and restored in `finally`.
|
|
126
|
+
- Dynamic `import()` is used where module caching matters, but PathHelper reads env vars at call time so cached modules work correctly.
|
|
127
|
+
|
|
128
|
+
## PathHelper Rules (`commands/utils/PathHelper.js`)
|
|
129
|
+
|
|
130
|
+
### Project Root Resolution
|
|
131
|
+
`getProjectRoot(moduleUrl)` resolves in this order:
|
|
132
|
+
1. `process.env.INIT_CWD` — set by npm or by `withTestProject` during tests
|
|
133
|
+
2. `process.cwd()` — current working directory
|
|
134
|
+
3. `candidates(moduleUrl)` — heuristic: walk up `../../` and `../../../../` from module location, check for `src/` or `api/`
|
|
135
|
+
|
|
136
|
+
### Functions
|
|
137
|
+
|
|
138
|
+
| Function | Returns | Notes |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `getProjectRoot(moduleUrl)` | Resolved project root path | |
|
|
141
|
+
| `getSrcPath(moduleUrl, ...seg)` | `<root>/src/[...seg]` | |
|
|
142
|
+
| `getApiPath(moduleUrl, ...seg)` | `<root>/api/[...seg]` | |
|
|
143
|
+
| `getDistPath(moduleUrl, ...seg)` | `<root>/dist/[...seg]` | |
|
|
144
|
+
| `getPath(moduleUrl, ...seg)` | `<root>/[...seg]` | General purpose |
|
|
145
|
+
| `getConfigPath(moduleUrl, root?)` | `src/sliceConfig.json` | Optional explicit root param |
|
|
146
|
+
| `getComponentsJsPath(moduleUrl, root?)` | `src/Components/components.js` | Optional explicit root param |
|
|
147
|
+
| `joinRoot(root, ...seg)` | `<root>/[...seg]` | No moduleUrl needed, pure path join |
|
|
148
|
+
|
|
149
|
+
### Critical Rules
|
|
150
|
+
|
|
151
|
+
1. **`import.meta.url` must be passed** as first argument to all PathHelper functions (except `joinRoot`).
|
|
152
|
+
2. **Explicit root parameter** (`getConfigPath`, `getComponentsJsPath`) is used by `types/types.js` when generating types for a non-cwd project. This keeps functions testable without global state.
|
|
153
|
+
3. **`INIT_CWD` is the primary mechanism** for project root resolution. It's set by npm lifecycle scripts and by `withTestProject`.
|
|
154
|
+
4. **`candidates()` fallback** only works when the CLI is installed inside a project that has `src/` or `api/`. This is intentionally limited.
|
|
155
|
+
|
|
156
|
+
## Code Quality Standards
|
|
157
|
+
|
|
158
|
+
### ES Modules Only
|
|
159
|
+
- `"type": "module"` in `package.json`
|
|
160
|
+
- Use `import`/`export` everywhere
|
|
161
|
+
- NO `require()`, NO `__dirname` at module scope (use `path.dirname(fileURLToPath(import.meta.url))` inline where needed)
|
|
162
|
+
|
|
163
|
+
### No eval()
|
|
164
|
+
- `eval()` has been fully replaced with `JSON.parse()` for reading `components.js` files
|
|
165
|
+
- Components are written via `JSON.stringify()`, so content is always valid JSON
|
|
166
|
+
- Use `JSON.parse()` or the AST-based `ComponentRegistry` for component registry parsing
|
|
167
|
+
|
|
168
|
+
### Error Messages
|
|
169
|
+
- Bare error messages (just the error message without context) must NOT be used
|
|
170
|
+
- Always wrap errors with context: `Print.error('Context:', error.message)`
|
|
171
|
+
- Use `Print.error()` / `Print.success()` / `Print.info()` / `Print.warning()` instead of raw `console.log`/`console.error`
|
|
172
|
+
- EXCEPTION: Formatted help/command listing output can use `console.log` directly (avoids `ℹ️ Info:` prefix pollution)
|
|
173
|
+
|
|
174
|
+
### Empty Catch Blocks
|
|
175
|
+
- Silent catches are acceptable ONLY for:
|
|
176
|
+
- Non-critical operations (update checks, optional config reads)
|
|
177
|
+
- Graceful degradation paths
|
|
178
|
+
- All silent catches MUST have a comment explaining why: `catch { /* intentional: non-critical */ }`
|
|
179
|
+
|
|
180
|
+
### Port Resolution (startServer)
|
|
181
|
+
Priority order:
|
|
182
|
+
1. `--port` CLI flag (if provided by user)
|
|
183
|
+
2. `config.server.port` from `sliceConfig.json`
|
|
184
|
+
3. Hardcoded `3000` fallback
|
|
185
|
+
|
|
186
|
+
Commander `.option()` defaults must NOT override config values. Pass `undefined` when flag is not provided:
|
|
187
|
+
```js
|
|
188
|
+
port: options.port ? parseInt(options.port) : undefined
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Dependency Injection for Testability
|
|
192
|
+
- Functions that need a project root accept it as a parameter (`projectRoot`, `root`)
|
|
193
|
+
- PathHelper functions that accept an explicit root param enable testing without INIT_CWD gymnastics
|
|
194
|
+
- Avoid reading `process.env.INIT_CWD` or `process.cwd()` directly inside business logic; use PathHelper
|
|
195
|
+
|
|
196
|
+
## CLI Architecture (client.js)
|
|
197
|
+
|
|
198
|
+
### Command Registration Pattern
|
|
199
|
+
```js
|
|
200
|
+
sliceClient
|
|
201
|
+
.command("mycommand")
|
|
202
|
+
.description("...")
|
|
203
|
+
.option("-x, --flag <value>", "...")
|
|
204
|
+
.action(async (options) => {
|
|
205
|
+
// 1. Handle --yes / non-interactive flags before prompts
|
|
206
|
+
// 2. Prompt for missing required values
|
|
207
|
+
// 3. Delegate to command implementation
|
|
208
|
+
await runWithVersionCheck(async () => {
|
|
209
|
+
await myCommandImplementation(options);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `runWithVersionCheck(commandFunction)`
|
|
215
|
+
- Wraps every command action
|
|
216
|
+
- Responsibilities:
|
|
217
|
+
1. Fire-and-forget update notification (`notifyAvailableUpdates().catch(() => {})`)
|
|
218
|
+
2. Execute the command
|
|
219
|
+
3. Background version check (`checkForUpdates(false)` after 100ms delay)
|
|
220
|
+
- Does NOT block or prompt the user (pre-flight checks were removed)
|
|
221
|
+
- Errors are caught and logged via `Print.error()`
|
|
222
|
+
|
|
223
|
+
### Init Command (`slice init`)
|
|
224
|
+
- Default project name: `my-slice-app`
|
|
225
|
+
- `-y`/`--yes [name]` flag skips interactive prompts
|
|
226
|
+
- Creates project directory, `chdir`s into it, sets `INIT_CWD`
|
|
227
|
+
- Calls `initializeProject()` from `commands/init/init.js`
|
|
228
|
+
- Name normalization: trim → lowercase → spaces to hyphens → strip non-alphanumeric → collapse hyphens → trim hyphens
|
|
229
|
+
|
|
230
|
+
### Local CLI Delegation
|
|
231
|
+
- `maybeDelegateToLocalCli()` runs at module level before command parsing
|
|
232
|
+
- If a local `node_modules/slicejs-cli/` exists, spawns it instead of running the global CLI
|
|
233
|
+
- Controlled by `SLICE_NO_LOCAL_DELEGATION` env var
|
|
234
|
+
|
|
235
|
+
## Visual Component Registry
|
|
236
|
+
- Components downloaded from GitHub: `https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components/{category}/{Name}/{file}`
|
|
237
|
+
- Registry URL: same base + `src/Components/components.js`
|
|
238
|
+
- Starter visual components on init: Button, Link, Loading, MultiRoute, Navbar, NotFound, Route
|
|
239
|
+
- Components are registered by writing to `src/Components/components.js`
|
|
240
|
+
|
|
241
|
+
### File Download Rules (in `getAvailableComponents`)
|
|
242
|
+
- **Routing/navigation components** (`Route`, `MultiRoute`, `Link`): only `.js` file
|
|
243
|
+
- **Other Visual components** (Button, Loading, Navbar, etc.): `.js`, `.html`, `.css`
|
|
244
|
+
- **Service components** (FetchManager, etc.): only `.js` file
|
|
245
|
+
- File list is determined by hardcoded rules, NOT by checking the remote server
|
|
246
|
+
- If `.js` download fails → component install fails (fatal)
|
|
247
|
+
- If `.html`/`.css` fails → component install succeeds with warning
|
package/client.js
CHANGED
|
@@ -13,9 +13,9 @@ import updateManager from "./commands/utils/updateManager.js";
|
|
|
13
13
|
import fs from "fs";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { getProjectRoot, getSrcPath, getPath } from "./commands/utils/PathHelper.js";
|
|
17
|
+
import { loadConfigSync as sharedLoadConfigSync } from "./commands/utils/loadConfig.js";
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
19
|
import validations from "./commands/Validations.js";
|
|
20
20
|
import Print from "./commands/Print.js";
|
|
21
21
|
import build from './commands/build/build.js';
|
|
@@ -30,67 +30,14 @@ import {
|
|
|
30
30
|
|
|
31
31
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
|
|
33
|
-
const loadConfig = () => {
|
|
34
|
-
try {
|
|
35
|
-
const configPath = getConfigPath(import.meta.url);
|
|
36
|
-
const rawData = fs.readFileSync(configPath, "utf-8");
|
|
37
|
-
return JSON.parse(rawData);
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
33
|
const getCategories = () => {
|
|
44
|
-
const config =
|
|
34
|
+
const config = sharedLoadConfigSync(import.meta.url);
|
|
45
35
|
return config && config.paths?.components ? Object.keys(config.paths.components) : [];
|
|
46
36
|
};
|
|
47
37
|
|
|
48
38
|
// Function to run version check for all commands
|
|
49
39
|
async function runWithVersionCheck(commandFunction, ...args) {
|
|
50
40
|
try {
|
|
51
|
-
const execAsync = promisify(exec);
|
|
52
|
-
await (async () => {
|
|
53
|
-
try {
|
|
54
|
-
const info = await updateManager.detectCliInstall();
|
|
55
|
-
if (info && info.type === 'global') {
|
|
56
|
-
const projectRoot = getProjectRoot(import.meta.url);
|
|
57
|
-
const pkgPath = path.join(projectRoot, 'package.json');
|
|
58
|
-
let hasPkg = fs.existsSync(pkgPath);
|
|
59
|
-
if (!hasPkg) {
|
|
60
|
-
const { confirmInit } = await inquirer.prompt([
|
|
61
|
-
{
|
|
62
|
-
type: 'confirm',
|
|
63
|
-
name: 'confirmInit',
|
|
64
|
-
message: 'No package.json found. Initialize npm in this project now?',
|
|
65
|
-
default: true
|
|
66
|
-
}
|
|
67
|
-
]);
|
|
68
|
-
if (confirmInit) {
|
|
69
|
-
await execAsync('npm init -y', { cwd: projectRoot });
|
|
70
|
-
hasPkg = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (hasPkg) {
|
|
74
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
75
|
-
const hasFramework = pkg.dependencies?.['slicejs-web-framework'];
|
|
76
|
-
if (!hasFramework) {
|
|
77
|
-
const { confirm } = await inquirer.prompt([
|
|
78
|
-
{
|
|
79
|
-
type: 'confirm',
|
|
80
|
-
name: 'confirm',
|
|
81
|
-
message: 'slicejs-web-framework is not installed in this project. Install it now?',
|
|
82
|
-
default: true
|
|
83
|
-
}
|
|
84
|
-
]);
|
|
85
|
-
if (confirm) {
|
|
86
|
-
await updateManager.updatePackage('slicejs-web-framework');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} catch {}
|
|
92
|
-
})();
|
|
93
|
-
|
|
94
41
|
updateManager.notifyAvailableUpdates().catch(() => {});
|
|
95
42
|
|
|
96
43
|
const result = await commandFunction(...args);
|
|
@@ -148,7 +95,59 @@ try {
|
|
|
148
95
|
sliceClient
|
|
149
96
|
.command("init")
|
|
150
97
|
.description("Initialize a new Slice.js project")
|
|
151
|
-
.
|
|
98
|
+
.option("-y, --yes [name]", "Skip prompts and initialize with project name")
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
let projectName = 'my-slice-app';
|
|
101
|
+
if (options.yes) {
|
|
102
|
+
projectName = typeof options.yes === 'string' ? options.yes : projectName;
|
|
103
|
+
} else {
|
|
104
|
+
const answers = await inquirer.prompt([
|
|
105
|
+
{
|
|
106
|
+
type: 'input',
|
|
107
|
+
name: 'projectName',
|
|
108
|
+
message: 'What is the name of your project?',
|
|
109
|
+
default: projectName,
|
|
110
|
+
filter: (input) => input.trim()
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.replace(/\s+/g, '-')
|
|
113
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
114
|
+
.replace(/-+/g, '-')
|
|
115
|
+
.replace(/^-|-$/g, ''),
|
|
116
|
+
validate: (input) => {
|
|
117
|
+
if (!input || !input.trim()) return 'Project name cannot be empty';
|
|
118
|
+
if (input.includes('/') || input.includes('\\')) return 'Use a simple name, not a path';
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]);
|
|
123
|
+
projectName = answers.projectName;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const projectDir = path.resolve(projectName);
|
|
127
|
+
|
|
128
|
+
if (fs.existsSync(projectDir)) {
|
|
129
|
+
const contents = fs.readdirSync(projectDir);
|
|
130
|
+
if (contents.length > 0) {
|
|
131
|
+
const { overwrite } = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'confirm',
|
|
134
|
+
name: 'overwrite',
|
|
135
|
+
message: `Directory "${answers.projectName}" already exists and is not empty. Continue?`,
|
|
136
|
+
default: false
|
|
137
|
+
}
|
|
138
|
+
]);
|
|
139
|
+
if (!overwrite) {
|
|
140
|
+
Print.info('Initialization cancelled.');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
process.chdir(projectDir);
|
|
149
|
+
process.env.INIT_CWD = projectDir;
|
|
150
|
+
|
|
152
151
|
await runWithVersionCheck(() => {
|
|
153
152
|
initializeProject();
|
|
154
153
|
return Promise.resolve();
|
|
@@ -206,7 +205,7 @@ buildCommand
|
|
|
206
205
|
sliceClient
|
|
207
206
|
.command("dev")
|
|
208
207
|
.description("Start development server with hot reload enabled by default")
|
|
209
|
-
.option("-p, --port <port>", "Port for development server"
|
|
208
|
+
.option("-p, --port <port>", "Port for development server")
|
|
210
209
|
.option("--no-hmr", "Disable hot module reload (enabled by default)")
|
|
211
210
|
.action(async (options) => {
|
|
212
211
|
const prevEnv = process.env.NODE_ENV;
|
|
@@ -215,7 +214,7 @@ sliceClient
|
|
|
215
214
|
await runWithVersionCheck(async () => {
|
|
216
215
|
await startServer({
|
|
217
216
|
mode: 'development',
|
|
218
|
-
port: parseInt(options.port),
|
|
217
|
+
port: options.port ? parseInt(options.port) : undefined,
|
|
219
218
|
watch: options.hmr
|
|
220
219
|
});
|
|
221
220
|
});
|
|
@@ -228,7 +227,7 @@ sliceClient
|
|
|
228
227
|
sliceClient
|
|
229
228
|
.command("start")
|
|
230
229
|
.description("Serve production files from dist/ (requires prior slice build)")
|
|
231
|
-
.option("-p, --port <port>", "Port for server"
|
|
230
|
+
.option("-p, --port <port>", "Port for server")
|
|
232
231
|
.action(async (options) => {
|
|
233
232
|
const prevEnv = process.env.NODE_ENV;
|
|
234
233
|
process.env.NODE_ENV = 'production';
|
|
@@ -236,7 +235,7 @@ sliceClient
|
|
|
236
235
|
await runWithVersionCheck(async () => {
|
|
237
236
|
await startServer({
|
|
238
237
|
mode: 'production',
|
|
239
|
-
port: parseInt(options.port)
|
|
238
|
+
port: options.port ? parseInt(options.port) : undefined
|
|
240
239
|
});
|
|
241
240
|
});
|
|
242
241
|
} finally {
|
|
@@ -367,7 +366,7 @@ componentCommand
|
|
|
367
366
|
}
|
|
368
367
|
|
|
369
368
|
const categoryPath = config.paths.components[category].path;
|
|
370
|
-
const fullPath =
|
|
369
|
+
const fullPath = getSrcPath(import.meta.url, categoryPath);
|
|
371
370
|
|
|
372
371
|
if (!fs.existsSync(fullPath)) {
|
|
373
372
|
Print.error(`Category path does not exist: ${categoryPath}`);
|
|
@@ -637,7 +636,7 @@ sliceClient
|
|
|
637
636
|
});
|
|
638
637
|
|
|
639
638
|
|
|
640
|
-
// Custom help -
|
|
639
|
+
// Custom help - SIMPLIFIED for development only
|
|
641
640
|
sliceClient.addHelpText('after', `
|
|
642
641
|
Common Usage Examples:
|
|
643
642
|
slice init - Initialize new Slice.js project
|
package/commands/Print.js
CHANGED
|
@@ -43,7 +43,7 @@ export default class Print {
|
|
|
43
43
|
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Methods for CLI-specific context
|
|
47
47
|
static componentSuccess(componentName, action = 'processed') {
|
|
48
48
|
console.log(chalk.green(`✅ ${componentName} ${action} successfully!`));
|
|
49
49
|
}
|
|
@@ -56,12 +56,8 @@ export default class Print {
|
|
|
56
56
|
console.log(chalk.cyan(` 📥 Downloading ${fileName}...`));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
static
|
|
60
|
-
console.
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static downloadError(fileName, error) {
|
|
64
|
-
console.error(chalk.red(` ❌ Error downloading ${fileName}: ${error}`));
|
|
59
|
+
static downloadError(fileName) {
|
|
60
|
+
console.error(chalk.red(` ❌ ${fileName}`));
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
static registryUpdate(message) {
|
|
@@ -93,7 +89,7 @@ export default class Print {
|
|
|
93
89
|
Print.separator();
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
//
|
|
92
|
+
// Method to show minification results
|
|
97
93
|
static minificationResult(filename, originalSize, minifiedSize, savingsPercent) {
|
|
98
94
|
const originalKB = (originalSize / 1024).toFixed(1);
|
|
99
95
|
const minifiedKB = (minifiedSize / 1024).toFixed(1);
|
|
@@ -102,12 +98,12 @@ export default class Print {
|
|
|
102
98
|
console.log(chalk.gray(` ${originalKB}KB → ${minifiedKB}KB (${savingsPercent}% saved)`));
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
//
|
|
101
|
+
// Method to show build progress
|
|
106
102
|
static buildProgress(message) {
|
|
107
103
|
console.log(chalk.cyan(`🔄 ${message}`));
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
//
|
|
106
|
+
// Method to show server statistics
|
|
111
107
|
static serverStats(mode, port, directory) {
|
|
112
108
|
Print.newLine();
|
|
113
109
|
console.log(chalk.magenta(`🌐 Server Configuration:`));
|
|
@@ -117,7 +113,7 @@ export default class Print {
|
|
|
117
113
|
Print.newLine();
|
|
118
114
|
}
|
|
119
115
|
|
|
120
|
-
//
|
|
116
|
+
// Method to show the server is ready with highlighted URL
|
|
121
117
|
static serverReady(port) {
|
|
122
118
|
Print.newLine();
|
|
123
119
|
console.log(chalk.bgGreen.black.bold(' ✓ SERVER READY '));
|
|
@@ -129,7 +125,7 @@ export default class Print {
|
|
|
129
125
|
Print.newLine();
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
//
|
|
128
|
+
// Method to show server status during startup
|
|
133
129
|
static serverStatus(status, message = '') {
|
|
134
130
|
const icons = {
|
|
135
131
|
checking: '🔍',
|
|
@@ -151,17 +147,17 @@ export default class Print {
|
|
|
151
147
|
console.log(color(`${icon} ${displayMessage}`));
|
|
152
148
|
}
|
|
153
149
|
|
|
154
|
-
//
|
|
150
|
+
// Method to show port checking status
|
|
155
151
|
static checkingPort(port) {
|
|
156
152
|
console.log(chalk.cyan(`🔍 Checking port ${port}...`));
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
//
|
|
155
|
+
// New: Debug method
|
|
160
156
|
static debug(message) {
|
|
161
157
|
console.log(chalk.gray(`🐛 DEBUG: ${message}`));
|
|
162
158
|
}
|
|
163
159
|
|
|
164
|
-
//
|
|
160
|
+
// New: Verbose logging method
|
|
165
161
|
static verbose(message) {
|
|
166
162
|
console.log(chalk.gray(`📝 ${message}`));
|
|
167
163
|
}
|
package/commands/Validations.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
3
|
+
import Print from './Print.js';
|
|
4
|
+
import { getComponentsJsPath } from './utils/PathHelper.js';
|
|
5
|
+
import { loadConfigSync } from './utils/loadConfig.js';
|
|
7
6
|
|
|
8
7
|
class Validations {
|
|
9
8
|
constructor() {
|
|
@@ -26,25 +25,13 @@ class Validations {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
isValidComponentName(componentName) {
|
|
29
|
-
//
|
|
28
|
+
// Regex to check if the name contains special characters
|
|
30
29
|
const regex = /^[a-zA-Z][a-zA-Z0-9]*$/;
|
|
31
30
|
return regex.test(componentName);
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
loadConfig() {
|
|
35
|
-
|
|
36
|
-
const configPath = getConfigPath(import.meta.url);
|
|
37
|
-
if (!fs.existsSync(configPath)) {
|
|
38
|
-
// Return null silently - let commands handle missing config if needed
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
42
|
-
|
|
43
|
-
return JSON.parse(rawData);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('\x1b[31m', `❌ Error loading configuration: ${error.message}`, '\x1b[0m');
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
34
|
+
return loadConfigSync(import.meta.url);
|
|
48
35
|
}
|
|
49
36
|
|
|
50
37
|
getCategories() {
|
|
@@ -80,19 +67,21 @@ class Validations {
|
|
|
80
67
|
const componentFilePath = getComponentsJsPath(import.meta.url);
|
|
81
68
|
|
|
82
69
|
if (!fs.existsSync(componentFilePath)) {
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
Print.error('components.js not found in expected path');
|
|
71
|
+
Print.info('Run "slice component list" to generate components.js');
|
|
85
72
|
return false;
|
|
86
73
|
}
|
|
87
74
|
|
|
88
75
|
const fileContent = fs.readFileSync(componentFilePath, 'utf-8');
|
|
89
|
-
const
|
|
76
|
+
const match = fileContent.match(/const components = ({[\s\S]*?});/);
|
|
77
|
+
if (!match) return false;
|
|
78
|
+
const components = JSON.parse(match[1]);
|
|
90
79
|
|
|
91
80
|
return components.hasOwnProperty(componentName);
|
|
92
81
|
|
|
93
82
|
} catch (error) {
|
|
94
|
-
|
|
95
|
-
|
|
83
|
+
Print.error(`Error checking component existence: ${error.message}`);
|
|
84
|
+
Print.info('The components.js file may be corrupted');
|
|
96
85
|
return false;
|
|
97
86
|
}
|
|
98
87
|
}
|