@chemx/starter-kit 26.9.16-668 → 26.9.17-309
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 +4 -0
- package/assets/chemx-starter-kit-logo.gif +0 -0
- package/blueprints/_view-template.scss +24 -0
- package/blueprints/atoms/a-button.vue +43 -0
- package/blueprints/molecule-capsule/_m-sample-card.scss +75 -0
- package/blueprints/molecule-capsule/index.ts +2 -0
- package/blueprints/molecule-capsule/m-chemx-badge/_m-chemx-badge.scss +76 -0
- package/blueprints/molecule-capsule/m-chemx-badge/index.ts +3 -0
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.spec.ts +74 -0
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.tsx +39 -0
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.vue +42 -0
- package/blueprints/molecule-capsule/m-chemx-badge/types.d.ts +12 -0
- package/blueprints/molecule-capsule/m-sample-card.spec.ts +74 -0
- package/blueprints/molecule-capsule/m-sample-card.tsx +72 -0
- package/blueprints/molecule-capsule/m-tab-button/_m-tab-button.scss +49 -0
- package/blueprints/molecule-capsule/m-tab-button/index.ts +3 -0
- package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +43 -0
- package/blueprints/molecule-capsule/m-tab-button/m-tab-button.spec.ts +40 -0
- package/blueprints/molecule-capsule/m-tab-button/m-tab-button.vue +38 -0
- package/blueprints/molecule-capsule/m-tab-button/types.d.ts +14 -0
- package/blueprints/molecule-capsule/types.d.ts +12 -0
- package/blueprints/shims-vue.d.ts +4 -0
- package/blueprints/view-template.tsx +20 -0
- package/blueprints/workflows/chemx-audit.yml +79 -0
- package/cli/audit/autofix.js +4 -5
- package/cli/audit/pattern-detector.js +90 -3
- package/cli/audit/pattern-detector.spec.js +36 -1
- package/cli/audit/rules-predicates.js +3 -2
- package/cli/audit-preflight.spec.js +11 -0
- package/cli/generator-templates/controller.js +36 -0
- package/cli/generator-templates/hooks.js +60 -0
- package/cli/generator-templates/naming.js +14 -0
- package/cli/generator-templates/react.js +63 -0
- package/cli/generator-templates/specs.js +38 -0
- package/cli/generator-templates/styles.js +64 -0
- package/cli/generator-templates/svelte.js +66 -0
- package/cli/generator-templates/types.js +29 -0
- package/cli/generator-templates/views.js +76 -0
- package/cli/generator-templates/vue.js +62 -0
- package/cli/generator-templates.d.ts +35 -0
- package/cli/generator-templates.js +28 -480
- package/cli/generator-templates.spec.js +108 -0
- package/cli/generator.js +2 -2
- package/cli/generator.spec.js +61 -1
- package/cli/index.js +3 -0
- package/cli/installer-templates.js +29 -8
- package/cli/license.js +22 -1
- package/cli/mcp/installer.js +31 -21
- package/cli/mcp/resources.js +114 -7
- package/cli/mcp/server.spec.js +34 -0
- package/cli/mcp/tools.js +14 -4
- package/cli/postinstall.js +27 -0
- package/cli/project-detector.js +20 -2
- package/cli/project-detector.spec.js +19 -0
- package/cli/reader.js +18 -4
- package/cli/scaffold.js +19 -3
- package/cli/terminal.js +36 -1
- package/docs/CHANGELOG.md +10 -0
- package/package.json +8 -5
- package/scripts/postinstall.mjs +27 -0
- package/scripts/pre-commit.sh +135 -0
- package/scripts/publish-both.mjs +108 -0
|
@@ -1,480 +1,28 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
disabled={!canProceed}
|
|
30
|
-
onClick={handleAction}
|
|
31
|
-
>
|
|
32
|
-
{variant}
|
|
33
|
-
</button>`;
|
|
34
|
-
|
|
35
|
-
return `import React from 'react';
|
|
36
|
-
${atomImport}import type { ${pascalName}Props } from './types';
|
|
37
|
-
import { use${pascalName}Controller } from './${name}.controller';
|
|
38
|
-
|
|
39
|
-
export const ${pascalName}: React.FC<${pascalName}Props> = ({
|
|
40
|
-
title,
|
|
41
|
-
subtitle,
|
|
42
|
-
variant = 'standard',
|
|
43
|
-
onAction
|
|
44
|
-
}) => {
|
|
45
|
-
const { state, canProceed, descriptor, handleAction } = use${pascalName}Controller({
|
|
46
|
-
initialState: { status: 'idle' },
|
|
47
|
-
onAction
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="${name}">
|
|
52
|
-
<div className="${name}__header">
|
|
53
|
-
<h3 className="${name}__title">{title}</h3>
|
|
54
|
-
{subtitle && <p className="${name}__subtitle">{subtitle}</p>}
|
|
55
|
-
<span className={descriptor.className}>{descriptor.text}</span>
|
|
56
|
-
</div>
|
|
57
|
-
<div className="${name}__body">
|
|
58
|
-
${actionButton}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export default ${pascalName};
|
|
65
|
-
`;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const buildVueComponent = (name, pascalName, options = {}) => {
|
|
69
|
-
const atomsPackage = options.atomsPackage || null;
|
|
70
|
-
const actionButton = atomsPackage
|
|
71
|
-
? ` <a-button
|
|
72
|
-
:disabled="!canProceed"
|
|
73
|
-
@click="handleAction"
|
|
74
|
-
>
|
|
75
|
-
{{ variant }}
|
|
76
|
-
</a-button>`
|
|
77
|
-
: ` <button
|
|
78
|
-
type="button"
|
|
79
|
-
class="${name}__action"
|
|
80
|
-
:disabled="!canProceed"
|
|
81
|
-
@click="handleAction"
|
|
82
|
-
>
|
|
83
|
-
{{ variant }}
|
|
84
|
-
</button>`;
|
|
85
|
-
|
|
86
|
-
return `<script setup lang="ts">
|
|
87
|
-
import type { ${pascalName}Props, ${pascalName}Emits } from './types';
|
|
88
|
-
import { use${pascalName}Controller } from './${name}.controller';
|
|
89
|
-
|
|
90
|
-
const props = withDefaults(defineProps<${pascalName}Props>(), {
|
|
91
|
-
variant: 'standard'
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const emit = defineEmits<${pascalName}Emits>();
|
|
95
|
-
|
|
96
|
-
const { state, canProceed, descriptor, handleAction } = use${pascalName}Controller({
|
|
97
|
-
initialState: { status: 'idle' },
|
|
98
|
-
onAction: () => emit('action', props.title)
|
|
99
|
-
});
|
|
100
|
-
</script>
|
|
101
|
-
|
|
102
|
-
<template>
|
|
103
|
-
<div class="${name}">
|
|
104
|
-
<div class="${name}__header">
|
|
105
|
-
<h3 class="${name}__title">{{ title }}</h3>
|
|
106
|
-
<p v-if="subtitle" class="${name}__subtitle">{{ subtitle }}</p>
|
|
107
|
-
<span :class="descriptor.className">{{ descriptor.text }}</span>
|
|
108
|
-
</div>
|
|
109
|
-
<div class="${name}__body">
|
|
110
|
-
${actionButton}
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</template>
|
|
114
|
-
|
|
115
|
-
<style lang="scss" scoped>
|
|
116
|
-
@use './_${name}.scss';
|
|
117
|
-
</style>
|
|
118
|
-
`;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const buildSvelteComponent = (name, pascalName, options = {}) => {
|
|
122
|
-
const atomsPackage = options.atomsPackage || null;
|
|
123
|
-
const atomImport = atomsPackage ? `import { AtomButton } from '${atomsPackage}';\n` : '';
|
|
124
|
-
const actionButton = atomsPackage
|
|
125
|
-
? ` <AtomButton
|
|
126
|
-
disabled={!controller.canProceed}
|
|
127
|
-
onclick={controller.handleAction}
|
|
128
|
-
>
|
|
129
|
-
{variant}
|
|
130
|
-
</AtomButton>`
|
|
131
|
-
: ` <button
|
|
132
|
-
type="button"
|
|
133
|
-
class="${name}__action"
|
|
134
|
-
disabled={!controller.canProceed}
|
|
135
|
-
onclick={controller.handleAction}
|
|
136
|
-
>
|
|
137
|
-
{variant}
|
|
138
|
-
</button>`;
|
|
139
|
-
|
|
140
|
-
return `<script lang="ts">
|
|
141
|
-
import type { ${pascalName}Props } from './types';
|
|
142
|
-
${atomImport}import { create${pascalName}Controller } from './${name}.controller';
|
|
143
|
-
|
|
144
|
-
const {
|
|
145
|
-
title,
|
|
146
|
-
subtitle = '',
|
|
147
|
-
variant = 'standard',
|
|
148
|
-
onAction
|
|
149
|
-
}: ${pascalName}Props = $props();
|
|
150
|
-
|
|
151
|
-
const controller = create${pascalName}Controller({
|
|
152
|
-
initialState: { status: 'idle' },
|
|
153
|
-
onAction
|
|
154
|
-
});
|
|
155
|
-
</script>
|
|
156
|
-
|
|
157
|
-
<div class="${name}">
|
|
158
|
-
<div class="${name}__header">
|
|
159
|
-
<h3 class="${name}__title">{title}</h3>
|
|
160
|
-
{#if subtitle}
|
|
161
|
-
<p class="${name}__subtitle">{subtitle}</p>
|
|
162
|
-
{/if}
|
|
163
|
-
<span class={controller.descriptor.className}>{controller.descriptor.text}</span>
|
|
164
|
-
</div>
|
|
165
|
-
<div class="${name}__body">
|
|
166
|
-
${actionButton}
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
<style lang="scss">
|
|
171
|
-
@use './_${name}.scss';
|
|
172
|
-
</style>
|
|
173
|
-
`;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
export const buildController = (name, pascalName) => `import { useState, useMemo } from 'react';
|
|
177
|
-
import type { ${pascalName}State, ${pascalName}Descriptor } from './types';
|
|
178
|
-
|
|
179
|
-
interface ControllerOptions {
|
|
180
|
-
readonly initialState?: ${pascalName}State;
|
|
181
|
-
readonly onAction?: () => void;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const use${pascalName}Controller = (options: ControllerOptions = {}) => {
|
|
185
|
-
const [state, setState] = useState<${pascalName}State>(
|
|
186
|
-
options.initialState || { status: 'idle' }
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const isIdle = state.status === 'idle';
|
|
190
|
-
const isPending = state.status === 'loading';
|
|
191
|
-
const canProceed = isIdle && !isPending;
|
|
192
|
-
|
|
193
|
-
const descriptor: ${pascalName}Descriptor = useMemo(() => {
|
|
194
|
-
if (state.status === 'loading') {
|
|
195
|
-
return { text: 'Loading...', className: '${name}__badge ${name}__badge--pending' };
|
|
196
|
-
}
|
|
197
|
-
if (state.status === 'active') {
|
|
198
|
-
return { text: 'Active', className: '${name}__badge ${name}__badge--active' };
|
|
199
|
-
}
|
|
200
|
-
return { text: 'Ready', className: '${name}__badge ${name}__badge--ready' };
|
|
201
|
-
}, [state.status]);
|
|
202
|
-
|
|
203
|
-
const handleAction = () => {
|
|
204
|
-
if (!canProceed) return;
|
|
205
|
-
setState({ status: 'active', activeId: 'item-1' });
|
|
206
|
-
options.onAction?.();
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
return { state, canProceed, descriptor, handleAction };
|
|
210
|
-
};
|
|
211
|
-
`;
|
|
212
|
-
|
|
213
|
-
export const buildPropsType = (name, pascalName) => `export interface ${pascalName}Props {
|
|
214
|
-
readonly title: string;
|
|
215
|
-
readonly subtitle?: string;
|
|
216
|
-
readonly variant?: 'standard' | 'highlight';
|
|
217
|
-
readonly onAction?: () => void;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export interface ${pascalName}Emits {
|
|
221
|
-
(e: 'action', title: string): void;
|
|
222
|
-
}
|
|
223
|
-
`;
|
|
224
|
-
|
|
225
|
-
export const buildStateType = (name, pascalName) => `export type ${pascalName}State =
|
|
226
|
-
| { readonly status: 'idle' }
|
|
227
|
-
| { readonly status: 'loading'; readonly progress: number }
|
|
228
|
-
| { readonly status: 'active'; readonly activeId: string }
|
|
229
|
-
| { readonly status: 'fault'; readonly faultMessage: string };
|
|
230
|
-
|
|
231
|
-
export interface ${pascalName}Descriptor {
|
|
232
|
-
readonly text: string;
|
|
233
|
-
readonly className: string;
|
|
234
|
-
}
|
|
235
|
-
`;
|
|
236
|
-
|
|
237
|
-
export const buildTypesIndex = (fileNames = ['props', 'state']) =>
|
|
238
|
-
fileNames.map((f) => `export type * from './${f}.d.ts';`).join('\n') + '\n';
|
|
239
|
-
|
|
240
|
-
export const buildTypes = (name, pascalName) => `${buildStateType(name, pascalName)}
|
|
241
|
-
${buildPropsType(name, pascalName)}`;
|
|
242
|
-
|
|
243
|
-
export const buildComponentSpec = (name, pascalName, hasController = true) => {
|
|
244
|
-
if (hasController) {
|
|
245
|
-
return `import { describe, it } from 'node:test';
|
|
246
|
-
import assert from 'node:assert/strict';
|
|
247
|
-
import { use${pascalName}Controller } from './${name}.controller.ts';
|
|
248
|
-
|
|
249
|
-
describe('${pascalName} Capsule Controller', () => {
|
|
250
|
-
it('exports pure controller hook', () => {
|
|
251
|
-
assert.equal(typeof use${pascalName}Controller, 'function');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return `import { describe, it } from 'node:test';
|
|
258
|
-
import assert from 'node:assert/strict';
|
|
259
|
-
|
|
260
|
-
describe('${pascalName} Atom Foundation', () => {
|
|
261
|
-
it('defines foundational UI atom contract', () => {
|
|
262
|
-
assert.ok(true);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
`;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
export const buildScss = (name) => `.${name} {
|
|
269
|
-
display: flex;
|
|
270
|
-
flex-direction: column;
|
|
271
|
-
padding: 16px;
|
|
272
|
-
background: rgba(19, 30, 58, 0.7);
|
|
273
|
-
backdrop-filter: blur(12px);
|
|
274
|
-
border: 1px solid rgba(98, 201, 255, 0.15);
|
|
275
|
-
border-radius: 8px;
|
|
276
|
-
|
|
277
|
-
&__header {
|
|
278
|
-
display: flex;
|
|
279
|
-
justify-content: space-between;
|
|
280
|
-
align-items: center;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
&__title {
|
|
284
|
-
margin: 0;
|
|
285
|
-
font-size: 16px;
|
|
286
|
-
color: #ffffff;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
&__subtitle {
|
|
290
|
-
margin: 4px 0 0;
|
|
291
|
-
font-size: 12px;
|
|
292
|
-
color: #94a3b8;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
&__badge {
|
|
296
|
-
font-size: 11px;
|
|
297
|
-
padding: 2px 8px;
|
|
298
|
-
border-radius: 4px;
|
|
299
|
-
background: #0b1329;
|
|
300
|
-
|
|
301
|
-
&--ready { color: #62c9ff; }
|
|
302
|
-
&--active { color: #4ade80; }
|
|
303
|
-
&--pending { color: #facc15; }
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
&__body {
|
|
307
|
-
margin-top: 14px;
|
|
308
|
-
display: flex;
|
|
309
|
-
justify-content: flex-end;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
&__action {
|
|
313
|
-
padding: 6px 14px;
|
|
314
|
-
border-radius: 4px;
|
|
315
|
-
background: #1e293b;
|
|
316
|
-
border: 1px solid #334155;
|
|
317
|
-
color: #ffffff;
|
|
318
|
-
cursor: pointer;
|
|
319
|
-
|
|
320
|
-
&:hover:not(:disabled) {
|
|
321
|
-
background: #334155;
|
|
322
|
-
border-color: #62c9ff;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
&:disabled {
|
|
326
|
-
opacity: 0.5;
|
|
327
|
-
cursor: not-allowed;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
`;
|
|
332
|
-
|
|
333
|
-
export const buildIndex = (name, pascalName, ext) => {
|
|
334
|
-
const compExport = ext === 'vue' || ext === 'svelte'
|
|
335
|
-
? `export { default as ${pascalName} } from './${name}.${ext}';`
|
|
336
|
-
: `export { ${pascalName} } from './${name}';`;
|
|
337
|
-
|
|
338
|
-
return `${compExport}
|
|
339
|
-
export { use${pascalName}Controller } from './${name}.controller';
|
|
340
|
-
export type * from './types';
|
|
341
|
-
`;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
export const buildHook = (name, camelName, pascalName) => `import { useState, useCallback, useEffect } from 'react';
|
|
345
|
-
import type { ${pascalName}HookOptions, ${pascalName}HookReturn } from './types';
|
|
346
|
-
|
|
347
|
-
export const ${camelName} = (options: ${pascalName}HookOptions = {}): ${pascalName}HookReturn => {
|
|
348
|
-
const [data, setData] = useState<string | null>(null);
|
|
349
|
-
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
350
|
-
const [error, setError] = useState<string | null>(null);
|
|
351
|
-
|
|
352
|
-
const execute = useCallback(async () => {
|
|
353
|
-
setIsLoading(true);
|
|
354
|
-
setError(null);
|
|
355
|
-
try {
|
|
356
|
-
setData('ready');
|
|
357
|
-
options.onSuccess?.();
|
|
358
|
-
} catch (err) {
|
|
359
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
360
|
-
setError(message);
|
|
361
|
-
} finally {
|
|
362
|
-
setIsLoading(false);
|
|
363
|
-
}
|
|
364
|
-
}, [options]);
|
|
365
|
-
|
|
366
|
-
useEffect(() => {
|
|
367
|
-
if (options.immediate) {
|
|
368
|
-
execute();
|
|
369
|
-
}
|
|
370
|
-
}, [options.immediate, execute]);
|
|
371
|
-
|
|
372
|
-
return { data, isLoading, error, execute };
|
|
373
|
-
};
|
|
374
|
-
`;
|
|
375
|
-
|
|
376
|
-
export const buildHookOptionsType = (pascalName) => `export interface ${pascalName}HookOptions {
|
|
377
|
-
readonly immediate?: boolean;
|
|
378
|
-
readonly onSuccess?: () => void;
|
|
379
|
-
}
|
|
380
|
-
`;
|
|
381
|
-
|
|
382
|
-
export const buildHookReturnType = (pascalName) => `export interface ${pascalName}HookReturn {
|
|
383
|
-
readonly data: string | null;
|
|
384
|
-
readonly isLoading: boolean;
|
|
385
|
-
readonly error: string | null;
|
|
386
|
-
readonly execute: () => Promise<void>;
|
|
387
|
-
}
|
|
388
|
-
`;
|
|
389
|
-
|
|
390
|
-
export const buildHookIndex = (name, camelName) => `export { ${camelName} } from './${name}';
|
|
391
|
-
export type * from './types';
|
|
392
|
-
`;
|
|
393
|
-
|
|
394
|
-
export const buildHookSpec = (name, camelName) => `import { describe, it } from 'node:test';
|
|
395
|
-
import assert from 'node:assert/strict';
|
|
396
|
-
import { ${camelName} } from './${name}.ts';
|
|
397
|
-
|
|
398
|
-
describe('${camelName} Hook Composable', () => {
|
|
399
|
-
it('exports pure hook function', () => {
|
|
400
|
-
assert.equal(typeof ${camelName}, 'function');
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
`;
|
|
404
|
-
|
|
405
|
-
export const buildReactView = (name, pascalName) => `import React from 'react';
|
|
406
|
-
import type { ${pascalName}ViewParams } from './types';
|
|
407
|
-
|
|
408
|
-
export const ${pascalName}View: React.FC<${pascalName}ViewParams> = ({
|
|
409
|
-
HeaderMolecule,
|
|
410
|
-
MainOrganism,
|
|
411
|
-
FooterMolecule
|
|
412
|
-
}) => (
|
|
413
|
-
<main className="${name}">
|
|
414
|
-
{HeaderMolecule}
|
|
415
|
-
{MainOrganism}
|
|
416
|
-
{FooterMolecule}
|
|
417
|
-
</main>
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
export default ${pascalName}View;
|
|
421
|
-
`;
|
|
422
|
-
|
|
423
|
-
export const buildVueView = (name, pascalName) => `<script setup lang="ts">
|
|
424
|
-
import type { ${pascalName}ViewParams } from './types';
|
|
425
|
-
|
|
426
|
-
defineProps<${pascalName}ViewParams>();
|
|
427
|
-
</script>
|
|
428
|
-
|
|
429
|
-
<template>
|
|
430
|
-
<main class="${name}">
|
|
431
|
-
<component :is="HeaderMolecule" />
|
|
432
|
-
<component :is="MainOrganism" />
|
|
433
|
-
<component :is="FooterMolecule" />
|
|
434
|
-
</main>
|
|
435
|
-
</template>
|
|
436
|
-
`;
|
|
437
|
-
|
|
438
|
-
export const buildSvelteView = (name, pascalName) => `<script lang="ts">
|
|
439
|
-
import type { ${pascalName}ViewParams } from './types';
|
|
440
|
-
|
|
441
|
-
const {
|
|
442
|
-
HeaderMolecule,
|
|
443
|
-
MainOrganism,
|
|
444
|
-
FooterMolecule
|
|
445
|
-
}: ${pascalName}ViewParams = $props();
|
|
446
|
-
</script>
|
|
447
|
-
|
|
448
|
-
<main class="${name}">
|
|
449
|
-
{@render HeaderMolecule?.()}
|
|
450
|
-
{@render MainOrganism?.()}
|
|
451
|
-
{@render FooterMolecule?.()}
|
|
452
|
-
</main>
|
|
453
|
-
`;
|
|
454
|
-
|
|
455
|
-
export const buildViewParamsType = (pascalName) => `export interface ${pascalName}ViewParams {
|
|
456
|
-
readonly HeaderMolecule?: unknown;
|
|
457
|
-
readonly MainOrganism?: unknown;
|
|
458
|
-
readonly FooterMolecule?: unknown;
|
|
459
|
-
}
|
|
460
|
-
`;
|
|
461
|
-
|
|
462
|
-
export const buildViewIndex = (name, pascalName, ext) => {
|
|
463
|
-
const compExport = ext === 'vue' || ext === 'svelte'
|
|
464
|
-
? `export { default as ${pascalName}View } from './${name}.${ext}';`
|
|
465
|
-
: `export { ${pascalName}View } from './${name}';`;
|
|
466
|
-
|
|
467
|
-
return `${compExport}
|
|
468
|
-
export type * from './types';
|
|
469
|
-
`;
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
export const buildViewSpec = (name, pascalName) => `import { describe, it } from 'node:test';
|
|
473
|
-
import assert from 'node:assert/strict';
|
|
474
|
-
|
|
475
|
-
describe('${pascalName}View Table of Contents', () => {
|
|
476
|
-
it('defines pure declarative blueprint layout', () => {
|
|
477
|
-
assert.ok(true);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
`;
|
|
1
|
+
export { toPascalCase, toCamelCase } from './generator-templates/naming.js';
|
|
2
|
+
export { buildReactComponent } from './generator-templates/react.js';
|
|
3
|
+
export { buildVueComponent } from './generator-templates/vue.js';
|
|
4
|
+
export { buildSvelteComponent } from './generator-templates/svelte.js';
|
|
5
|
+
export { buildController } from './generator-templates/controller.js';
|
|
6
|
+
export { buildComponentSpec, buildIndex } from './generator-templates/specs.js';
|
|
7
|
+
export {
|
|
8
|
+
buildPropsType,
|
|
9
|
+
buildStateType,
|
|
10
|
+
buildTypesIndex,
|
|
11
|
+
buildTypes
|
|
12
|
+
} from './generator-templates/types.js';
|
|
13
|
+
export { buildScss } from './generator-templates/styles.js';
|
|
14
|
+
export {
|
|
15
|
+
buildHook,
|
|
16
|
+
buildHookOptionsType,
|
|
17
|
+
buildHookReturnType,
|
|
18
|
+
buildHookIndex,
|
|
19
|
+
buildHookSpec
|
|
20
|
+
} from './generator-templates/hooks.js';
|
|
21
|
+
export {
|
|
22
|
+
buildReactView,
|
|
23
|
+
buildVueView,
|
|
24
|
+
buildSvelteView,
|
|
25
|
+
buildViewParamsType,
|
|
26
|
+
buildViewIndex,
|
|
27
|
+
buildViewSpec
|
|
28
|
+
} from './generator-templates/views.js';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
toPascalCase,
|
|
5
|
+
toCamelCase,
|
|
6
|
+
buildReactComponent,
|
|
7
|
+
buildVueComponent,
|
|
8
|
+
buildSvelteComponent,
|
|
9
|
+
buildController,
|
|
10
|
+
buildPropsType,
|
|
11
|
+
buildStateType,
|
|
12
|
+
buildTypesIndex,
|
|
13
|
+
buildTypes,
|
|
14
|
+
buildComponentSpec,
|
|
15
|
+
buildIndex,
|
|
16
|
+
buildScss,
|
|
17
|
+
buildHook,
|
|
18
|
+
buildHookOptionsType,
|
|
19
|
+
buildHookReturnType,
|
|
20
|
+
buildHookIndex,
|
|
21
|
+
buildHookSpec,
|
|
22
|
+
buildReactView,
|
|
23
|
+
buildVueView,
|
|
24
|
+
buildSvelteView,
|
|
25
|
+
buildViewParamsType,
|
|
26
|
+
buildViewIndex,
|
|
27
|
+
buildViewSpec
|
|
28
|
+
} from './generator-templates.js';
|
|
29
|
+
|
|
30
|
+
test('naming: toPascalCase and toCamelCase handle prefixes and hyphens', () => {
|
|
31
|
+
assert.equal(toPascalCase('m-user-avatar'), 'UserAvatar');
|
|
32
|
+
assert.equal(toPascalCase('use-spark-feed'), 'SparkFeed');
|
|
33
|
+
assert.equal(toPascalCase('v-dashboard-overview'), 'DashboardOverview');
|
|
34
|
+
assert.equal(toCamelCase('use-spark-feed'), 'useSparkFeed');
|
|
35
|
+
assert.equal(toCamelCase('m-spark-kpi'), 'sparkKpi');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('components: buildReactComponent handles options', () => {
|
|
39
|
+
const standard = buildReactComponent('m-card', 'Card');
|
|
40
|
+
assert.ok(standard.includes('export const Card: React.FC<CardProps>'));
|
|
41
|
+
assert.ok(standard.includes('useCardController'));
|
|
42
|
+
|
|
43
|
+
const withAtoms = buildReactComponent('m-card', 'Card', { atomsPackage: '@chemx/x-atoms' });
|
|
44
|
+
assert.ok(withAtoms.includes("import { AtomButton } from '@chemx/x-atoms';"));
|
|
45
|
+
|
|
46
|
+
const lean = buildReactComponent('a-pill', 'Pill', { hasController: false });
|
|
47
|
+
assert.ok(!lean.includes('usePillController'));
|
|
48
|
+
assert.ok(lean.includes('const canProceed = true;'));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('components: buildVueComponent and buildSvelteComponent handle options', () => {
|
|
52
|
+
const vue = buildVueComponent('m-card', 'Card', { atomsPackage: '@chemx/x-atoms' });
|
|
53
|
+
assert.ok(vue.includes('<a-button'));
|
|
54
|
+
|
|
55
|
+
const svelte = buildSvelteComponent('m-card', 'Card', { atomsPackage: '@chemx/x-atoms' });
|
|
56
|
+
assert.ok(svelte.includes('<AtomButton'));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('controller: buildController generates valid react hook code', () => {
|
|
60
|
+
const code = buildController('m-card', 'Card');
|
|
61
|
+
assert.ok(code.includes('export const useCardController ='));
|
|
62
|
+
assert.ok(code.includes('useState<CardState>'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('types: buildPropsType, buildStateType, and buildTypes produce contract definitions', () => {
|
|
66
|
+
const types = buildTypes('m-card', 'Card');
|
|
67
|
+
assert.ok(types.includes('CardState'));
|
|
68
|
+
assert.ok(types.includes('CardProps'));
|
|
69
|
+
assert.ok(buildTypesIndex().includes("export type * from './props.d.ts';"));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('specs and styles: buildComponentSpec, buildIndex, and buildScss generate expected assets', () => {
|
|
73
|
+
const specWithCtrl = buildComponentSpec('m-card', 'Card', true);
|
|
74
|
+
assert.ok(specWithCtrl.includes('useCardController'));
|
|
75
|
+
const specLean = buildComponentSpec('a-pill', 'Pill', false);
|
|
76
|
+
assert.ok(specLean.includes('Pill Atom Foundation'));
|
|
77
|
+
|
|
78
|
+
const idxVue = buildIndex('m-card', 'Card', 'vue');
|
|
79
|
+
assert.ok(idxVue.includes("export { default as Card } from './m-card.vue';"));
|
|
80
|
+
|
|
81
|
+
const scss = buildScss('m-card');
|
|
82
|
+
assert.ok(scss.includes('.m-card {'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('hooks: buildHook and related generators produce full composable suite', () => {
|
|
86
|
+
const hook = buildHook('use-feed', 'useFeed', 'Feed');
|
|
87
|
+
assert.ok(hook.includes('export const useFeed ='));
|
|
88
|
+
assert.ok(buildHookOptionsType('Feed').includes('FeedHookOptions'));
|
|
89
|
+
assert.ok(buildHookReturnType('Feed').includes('FeedHookReturn'));
|
|
90
|
+
assert.ok(buildHookIndex('use-feed', 'useFeed').includes("export { useFeed } from './use-feed';"));
|
|
91
|
+
assert.ok(buildHookSpec('use-feed', 'useFeed').includes("describe('useFeed Hook Composable'"));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('views: buildReactView, buildVueView, buildSvelteView, and view helpers generate TOC templates', () => {
|
|
95
|
+
const reactView = buildReactView('v-home', 'Home');
|
|
96
|
+
assert.ok(reactView.includes('export const HomeView: React.FC<HomeViewParams>'));
|
|
97
|
+
assert.ok(reactView.includes('<main className="v-home">'));
|
|
98
|
+
|
|
99
|
+
const vueView = buildVueView('v-home', 'Home');
|
|
100
|
+
assert.ok(vueView.includes('<main class="v-home">'));
|
|
101
|
+
|
|
102
|
+
const svelteView = buildSvelteView('v-home', 'Home');
|
|
103
|
+
assert.ok(svelteView.includes('<main class="v-home">'));
|
|
104
|
+
|
|
105
|
+
assert.ok(buildViewParamsType('Home').includes('HomeViewParams'));
|
|
106
|
+
assert.ok(buildViewIndex('v-home', 'Home', 'tsx').includes("export { HomeView } from './v-home';"));
|
|
107
|
+
assert.ok(buildViewSpec('v-home', 'Home').includes("describe('HomeView Table of Contents'"));
|
|
108
|
+
});
|
package/cli/generator.js
CHANGED
|
@@ -151,10 +151,10 @@ export const createCapsuleFiles = ({
|
|
|
151
151
|
const specFile = `${capsuleName}.spec.ts`;
|
|
152
152
|
const hasController = !isLean && selectedTier.tier !== 'atom';
|
|
153
153
|
const installedFamily = detectInstalledFamily(cwd);
|
|
154
|
-
const templateOpts = { atomsPackage: installedFamily.atomsPackage };
|
|
154
|
+
const templateOpts = { atomsPackage: installedFamily.atomsPackage, hasController };
|
|
155
155
|
|
|
156
156
|
fs.writeFileSync(path.join(targetDir, compFile), selectedFramework.compBuilder(capsuleName, pascalName, templateOpts), 'utf-8');
|
|
157
|
-
fs.writeFileSync(path.join(targetDir, 'index.ts'), buildIndex(capsuleName, pascalName, selectedFramework.ext), 'utf-8');
|
|
157
|
+
fs.writeFileSync(path.join(targetDir, 'index.ts'), buildIndex(capsuleName, pascalName, selectedFramework.ext, hasController), 'utf-8');
|
|
158
158
|
fs.writeFileSync(path.join(targetDir, specFile), buildComponentSpec(capsuleName, pascalName, hasController), 'utf-8');
|
|
159
159
|
|
|
160
160
|
fs.writeFileSync(path.join(typesDir, 'props.d.ts'), buildPropsType(capsuleName, pascalName), 'utf-8');
|