@techninja/clearstack 0.2.6 → 0.2.8
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/docs/CONVENTIONS.md +44 -0
- package/lib/check.js +10 -4
- package/lib/package-gen.js +6 -13
- package/package.json +7 -14
- package/templates/fullstack/src/server.js +4 -4
- package/templates/shared/.configs/eslint.config.js +1 -1
- package/templates/shared/.configs/jsconfig.json +1 -1
- package/templates/shared/.env +1 -1
- package/templates/shared/docs/clearstack/CONVENTIONS.md +44 -0
- package/templates/shared/gitignore +2 -2
- package/templates/shared/scripts/build-icons.js +2 -2
- package/templates/shared/scripts/setup.js +14 -0
- package/templates/shared/scripts/test.js +37 -0
- package/templates/shared/scripts/vendor-deps.js +2 -2
- package/templates/shared/src/public/index.html +26 -0
- package/templates/shared/public/index.html +0 -26
package/docs/CONVENTIONS.md
CHANGED
|
@@ -213,6 +213,50 @@ function moveObj(o, dx, dy) { ... }
|
|
|
213
213
|
|
|
214
214
|
---
|
|
215
215
|
|
|
216
|
+
## npm Scripts: One Entry Point Per Domain
|
|
217
|
+
|
|
218
|
+
Every `package.json` script should be a single, discoverable entry point.
|
|
219
|
+
Avoid the `name:variant` colon pattern that fragments a domain across
|
|
220
|
+
multiple keys.
|
|
221
|
+
|
|
222
|
+
### Rules
|
|
223
|
+
|
|
224
|
+
- **One script per domain.** `test`, `spec`, `lint` — not `test:node`,
|
|
225
|
+
`test:browser`, `lint:fix`, `spec:code`, `spec:docs`.
|
|
226
|
+
- **Arguments over aliases.** `pnpm spec check code` instead of
|
|
227
|
+
`pnpm spec:code`. The CLI handles routing.
|
|
228
|
+
- **Interactive by default.** Running `pnpm spec` with no arguments shows
|
|
229
|
+
a menu of available actions. Users discover commands by using the tool.
|
|
230
|
+
- **Direct invocation for power users.** Once you know the subcommand,
|
|
231
|
+
skip the menu: `pnpm spec check`, `pnpm spec update`.
|
|
232
|
+
- **Self-documenting.** Each script's CLI should print usage when given
|
|
233
|
+
`help` or an unknown argument.
|
|
234
|
+
|
|
235
|
+
### Why
|
|
236
|
+
|
|
237
|
+
- Fewer script entries = less package.json bloat
|
|
238
|
+
- Discoverability through interactive menus beats memorizing key names
|
|
239
|
+
- Scripts grow via subcommands, not new `package.json` entries
|
|
240
|
+
- Consistent with how real CLIs work (`git`, `docker`, `npm` itself)
|
|
241
|
+
|
|
242
|
+
### Example
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"scripts": {
|
|
247
|
+
"start": "node src/server.js",
|
|
248
|
+
"dev": "node --watch --env-file=.env src/server.js",
|
|
249
|
+
"test": "node --test tests/*.test.js",
|
|
250
|
+
"spec": "clearstack"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`pnpm spec` → interactive menu. `pnpm spec check` → run checks.
|
|
256
|
+
`pnpm spec update` → sync docs. One entry, full access.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
216
260
|
## Session Retrospective
|
|
217
261
|
|
|
218
262
|
At the end of each implementation session, ask:
|
package/lib/check.js
CHANGED
|
@@ -20,7 +20,7 @@ function loadConfig(projectDir) {
|
|
|
20
20
|
docsMax: parseInt(env.SPEC_DOCS_MAX_LINES) || 500,
|
|
21
21
|
codeExt: (env.SPEC_CODE_EXTENSIONS || '.js,.css').split(','),
|
|
22
22
|
docsExt: (env.SPEC_DOCS_EXTENSIONS || '.md').split(','),
|
|
23
|
-
ignore: (env.SPEC_IGNORE_DIRS || 'node_modules,public/vendor,.git,.configs').split(','),
|
|
23
|
+
ignore: (env.SPEC_IGNORE_DIRS || 'node_modules,src/public/vendor,.git,.configs').split(','),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -100,16 +100,22 @@ function findFiles(dir, extensions, ignoreDirs, root) {
|
|
|
100
100
|
return results;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/** Run a shell command, report pass/fail. */
|
|
103
|
+
/** Run a shell command, report pass/fail. Filters node_modules errors. */
|
|
104
104
|
function runCmd(label, cmd, cwd) {
|
|
105
105
|
try {
|
|
106
106
|
execSync(cmd, { cwd, stdio: 'pipe' });
|
|
107
107
|
console.log(` ✅ ${label}`);
|
|
108
108
|
return true;
|
|
109
109
|
} catch (err) {
|
|
110
|
-
console.log(` ❌ ${label}`);
|
|
111
110
|
const out = (err.stdout || '') + (err.stderr || '');
|
|
112
|
-
|
|
111
|
+
const ownErrors = out.trim().split('\n')
|
|
112
|
+
.filter((l) => l.trim() && !l.includes('node_modules'));
|
|
113
|
+
if (ownErrors.length === 0) {
|
|
114
|
+
console.log(` ✅ ${label}`);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
console.log(` ❌ ${label}`);
|
|
118
|
+
ownErrors.forEach((l) => console.log(` ${l}`));
|
|
113
119
|
return false;
|
|
114
120
|
}
|
|
115
121
|
}
|
package/lib/package-gen.js
CHANGED
|
@@ -20,19 +20,12 @@ export async function writePackageJson(dest, vars, existing) {
|
|
|
20
20
|
...(isFullstack ? {
|
|
21
21
|
start: 'node src/server.js',
|
|
22
22
|
dev: 'node --watch --env-file=.env src/server.js',
|
|
23
|
-
} : {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
spec: 'clearstack
|
|
29
|
-
'spec:code': 'clearstack check code',
|
|
30
|
-
'spec:docs': 'clearstack check docs',
|
|
31
|
-
'spec:update': 'clearstack update',
|
|
32
|
-
lint: 'eslint --config .configs/eslint.config.js .',
|
|
33
|
-
'lint:fix': 'eslint --config .configs/eslint.config.js . --fix',
|
|
34
|
-
format: 'prettier --config .configs/.prettierrc --write src scripts tests',
|
|
35
|
-
typecheck: 'tsc --project .configs/jsconfig.json',
|
|
23
|
+
} : {
|
|
24
|
+
dev: 'npx serve src',
|
|
25
|
+
}),
|
|
26
|
+
postinstall: 'node scripts/setup.js',
|
|
27
|
+
test: 'node scripts/test.js',
|
|
28
|
+
spec: 'clearstack',
|
|
36
29
|
};
|
|
37
30
|
|
|
38
31
|
const specDeps = {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techninja/clearstack",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A no-build web component framework specification — scaffold, validate, and evolve spec-compliant projects",
|
|
6
6
|
"bin": {
|
|
7
|
-
"clearstack": "
|
|
7
|
+
"clearstack": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
@@ -20,24 +20,17 @@
|
|
|
20
20
|
"start": "node src/server.js",
|
|
21
21
|
"dev": "node --watch --env-file=.env src/server.js",
|
|
22
22
|
"setup": "node scripts/vendor-deps.js && node scripts/build-icons.js",
|
|
23
|
-
"test": "
|
|
24
|
-
"test:node": "node --test tests/*.test.js src/utils/*.test.js src/store/*.test.js",
|
|
25
|
-
"test:browser": "web-test-runner --config .configs/web-test-runner.config.js",
|
|
26
|
-
"test:watch": "web-test-runner --config .configs/web-test-runner.config.js --watch",
|
|
23
|
+
"test": "node --test tests/*.test.js src/utils/*.test.js src/store/*.test.js",
|
|
27
24
|
"spec": "node --env-file=.env scripts/spec.js",
|
|
28
|
-
"
|
|
29
|
-
"spec:docs": "node --env-file=.env scripts/spec.js docs",
|
|
30
|
-
"lint": "eslint --config .configs/eslint.config.js .",
|
|
31
|
-
"lint:fix": "eslint --config .configs/eslint.config.js . --fix",
|
|
25
|
+
"lint": "eslint --config .configs/eslint.config.js . --fix",
|
|
32
26
|
"format": "prettier --config .configs/.prettierrc --write src scripts tests",
|
|
33
|
-
"format:check": "prettier --config .configs/.prettierrc --check src scripts tests",
|
|
34
27
|
"typecheck": "tsc --project .configs/jsconfig.json",
|
|
35
|
-
"sync-docs": "node scripts/sync-docs.js",
|
|
36
28
|
"release": "node scripts/release.js",
|
|
37
|
-
"release:minor": "node scripts/release.js minor",
|
|
38
|
-
"release:major": "node scripts/release.js major",
|
|
39
29
|
"prepublishOnly": "node scripts/sync-docs.js"
|
|
40
30
|
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
41
34
|
"keywords": [
|
|
42
35
|
"web-components",
|
|
43
36
|
"no-build",
|
|
@@ -10,16 +10,16 @@ import { eventsRouter } from './api/events.js';
|
|
|
10
10
|
const app = express();
|
|
11
11
|
|
|
12
12
|
app.use(express.json());
|
|
13
|
-
app.use(express.static('public'));
|
|
14
|
-
app.use('/src', express.static('src'));
|
|
15
13
|
|
|
16
14
|
app.use('/api', eventsRouter);
|
|
17
15
|
app.use('/api', entityRouter);
|
|
18
16
|
|
|
17
|
+
app.use(express.static('src'));
|
|
18
|
+
|
|
19
19
|
// SPA fallback
|
|
20
20
|
app.use((req, res, next) => {
|
|
21
|
-
if (req.method === 'GET' && !req.path.includes('.')) {
|
|
22
|
-
return res.sendFile('index.html', { root: 'public' });
|
|
21
|
+
if (req.method === 'GET' && !req.path.includes('.') && !req.path.startsWith('/api')) {
|
|
22
|
+
return res.sendFile('index.html', { root: 'src/public' });
|
|
23
23
|
}
|
|
24
24
|
next();
|
|
25
25
|
});
|
package/templates/shared/.env
CHANGED
|
@@ -213,6 +213,50 @@ function moveObj(o, dx, dy) { ... }
|
|
|
213
213
|
|
|
214
214
|
---
|
|
215
215
|
|
|
216
|
+
## npm Scripts: One Entry Point Per Domain
|
|
217
|
+
|
|
218
|
+
Every `package.json` script should be a single, discoverable entry point.
|
|
219
|
+
Avoid the `name:variant` colon pattern that fragments a domain across
|
|
220
|
+
multiple keys.
|
|
221
|
+
|
|
222
|
+
### Rules
|
|
223
|
+
|
|
224
|
+
- **One script per domain.** `test`, `spec`, `lint` — not `test:node`,
|
|
225
|
+
`test:browser`, `lint:fix`, `spec:code`, `spec:docs`.
|
|
226
|
+
- **Arguments over aliases.** `pnpm spec check code` instead of
|
|
227
|
+
`pnpm spec:code`. The CLI handles routing.
|
|
228
|
+
- **Interactive by default.** Running `pnpm spec` with no arguments shows
|
|
229
|
+
a menu of available actions. Users discover commands by using the tool.
|
|
230
|
+
- **Direct invocation for power users.** Once you know the subcommand,
|
|
231
|
+
skip the menu: `pnpm spec check`, `pnpm spec update`.
|
|
232
|
+
- **Self-documenting.** Each script's CLI should print usage when given
|
|
233
|
+
`help` or an unknown argument.
|
|
234
|
+
|
|
235
|
+
### Why
|
|
236
|
+
|
|
237
|
+
- Fewer script entries = less package.json bloat
|
|
238
|
+
- Discoverability through interactive menus beats memorizing key names
|
|
239
|
+
- Scripts grow via subcommands, not new `package.json` entries
|
|
240
|
+
- Consistent with how real CLIs work (`git`, `docker`, `npm` itself)
|
|
241
|
+
|
|
242
|
+
### Example
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"scripts": {
|
|
247
|
+
"start": "node src/server.js",
|
|
248
|
+
"dev": "node --watch --env-file=.env src/server.js",
|
|
249
|
+
"test": "node --test tests/*.test.js",
|
|
250
|
+
"spec": "clearstack"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`pnpm spec` → interactive menu. `pnpm spec check` → run checks.
|
|
256
|
+
`pnpm spec update` → sync docs. One entry, full access.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
216
260
|
## Session Retrospective
|
|
217
261
|
|
|
218
262
|
At the end of each implementation session, ask:
|
|
@@ -12,7 +12,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
|
|
13
13
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
14
|
const ICONS_DIR = resolve(ROOT, 'node_modules/lucide-static/icons');
|
|
15
|
-
const OUT = resolve(ROOT, 'public/icons.json');
|
|
15
|
+
const OUT = resolve(ROOT, 'src/public/icons.json');
|
|
16
16
|
|
|
17
17
|
/** Icons used in the app — lucide name → app name */
|
|
18
18
|
const ICON_MAP = {
|
|
@@ -83,4 +83,4 @@ for (const [lucideName, appName] of Object.entries(ICON_MAP)) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
writeFileSync(OUT, JSON.stringify(icons, null, 2));
|
|
86
|
-
console.log(`✓ Built ${count} icons → public/icons.json`);
|
|
86
|
+
console.log(`✓ Built ${count} icons → src/public/icons.json`);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install setup — vendors dependencies and builds icon sprite.
|
|
5
|
+
* @module scripts/setup
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
12
|
+
|
|
13
|
+
await import(resolve(ROOT, 'scripts/vendor-deps.js'));
|
|
14
|
+
await import(resolve(ROOT, 'scripts/build-icons.js'));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test runner — finds and executes all .test.js files.
|
|
5
|
+
* @module scripts/test
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
|
+
|
|
15
|
+
/** @param {string} dir @returns {string[]} */
|
|
16
|
+
function findTests(dir) {
|
|
17
|
+
const results = [];
|
|
18
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
19
|
+
const full = resolve(dir, entry.name);
|
|
20
|
+
if (entry.name === 'node_modules' || entry.name === 'public') continue;
|
|
21
|
+
if (entry.isDirectory()) results.push(...findTests(full));
|
|
22
|
+
else if (entry.name.endsWith('.test.js')) results.push(full);
|
|
23
|
+
}
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const files = findTests(ROOT);
|
|
28
|
+
if (files.length === 0) {
|
|
29
|
+
console.log('No test files found.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
execSync(`node --test ${files.join(' ')}`, { cwd: ROOT, stdio: 'inherit' });
|
|
35
|
+
} catch {
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
@@ -10,7 +10,7 @@ import { resolve, dirname } from 'node:path';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
12
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
13
|
-
const VENDOR_DIR = resolve(ROOT, 'public/vendor');
|
|
13
|
+
const VENDOR_DIR = resolve(ROOT, 'src/public/vendor');
|
|
14
14
|
|
|
15
15
|
/** @type {{ name: string, src: string }[]} */
|
|
16
16
|
const DEPS = [{ name: 'hybrids', src: 'node_modules/hybrids/src' }];
|
|
@@ -21,5 +21,5 @@ for (const dep of DEPS) {
|
|
|
21
21
|
const src = resolve(ROOT, dep.src);
|
|
22
22
|
const dest = resolve(VENDOR_DIR, dep.name);
|
|
23
23
|
cpSync(src, dest, { recursive: true });
|
|
24
|
-
console.log(`✓ Vendored: ${dep.name} → public/vendor/${dep.name}/`);
|
|
24
|
+
console.log(`✓ Vendored: ${dep.name} → src/public/vendor/${dep.name}/`);
|
|
25
25
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles/reset.css">
|
|
8
|
+
<link rel="stylesheet" href="/styles/tokens.css">
|
|
9
|
+
<link rel="stylesheet" href="/styles/shared.css">
|
|
10
|
+
<link rel="stylesheet" href="/styles/buttons.css">
|
|
11
|
+
<link rel="stylesheet" href="/styles/forms.css">
|
|
12
|
+
<link rel="stylesheet" href="/styles/components.css">
|
|
13
|
+
|
|
14
|
+
<script type="importmap">
|
|
15
|
+
{
|
|
16
|
+
"imports": {
|
|
17
|
+
"hybrids": "/public/vendor/hybrids/index.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<app-router></app-router>
|
|
24
|
+
<script type="module" src="/router/index.js"></script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>{{name}}</title>
|
|
7
|
-
<link rel="stylesheet" href="/src/styles/reset.css">
|
|
8
|
-
<link rel="stylesheet" href="/src/styles/tokens.css">
|
|
9
|
-
<link rel="stylesheet" href="/src/styles/shared.css">
|
|
10
|
-
<link rel="stylesheet" href="/src/styles/buttons.css">
|
|
11
|
-
<link rel="stylesheet" href="/src/styles/forms.css">
|
|
12
|
-
<link rel="stylesheet" href="/src/styles/components.css">
|
|
13
|
-
|
|
14
|
-
<script type="importmap">
|
|
15
|
-
{
|
|
16
|
-
"imports": {
|
|
17
|
-
"hybrids": "/vendor/hybrids/index.js"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
</script>
|
|
21
|
-
</head>
|
|
22
|
-
<body>
|
|
23
|
-
<app-router></app-router>
|
|
24
|
-
<script type="module" src="/src/router/index.js"></script>
|
|
25
|
-
</body>
|
|
26
|
-
</html>
|