create-semaphor-app 0.1.2 → 0.1.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 +47 -3
- package/bin/create-semaphor-app.mjs +202 -6
- package/package.json +1 -1
- package/scripts/smoke-test.mjs +108 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Create a Semaphor Data App starter project.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx create-semaphor-app@latest
|
|
6
|
+
npx create-semaphor-app@latest
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
The CLI scaffolds the public Semaphor Data App Starter, installs local
|
|
@@ -17,9 +17,11 @@ minting, planning, code generation, validation, save, and publish.
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx create-semaphor-app@latest
|
|
20
|
+
npx create-semaphor-app@latest [app-name]
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
When `app-name` is omitted, the CLI creates `./semaphor-data-app`.
|
|
24
|
+
|
|
23
25
|
Options:
|
|
24
26
|
|
|
25
27
|
```text
|
|
@@ -30,10 +32,52 @@ Options:
|
|
|
30
32
|
--install-claude-plugin Install the Claude Code plugin without prompting.
|
|
31
33
|
--template <source> Starter source. Defaults to the public starter repo.
|
|
32
34
|
--template-ref <ref> Git branch/tag for the default starter repo. Defaults to main.
|
|
35
|
+
--shadcn-preset <preset> Apply a shadcn preset before adding Semaphor components.
|
|
36
|
+
--shadcn-base <base> Pass base or radix to shadcn init when using a preset.
|
|
37
|
+
--components <list> Add Semaphor registry components. Use none, query, metrics,
|
|
38
|
+
filters, card, table, matrix, recommended, all, or a comma-separated list.
|
|
33
39
|
--yes Use noninteractive defaults; skip optional plugin installs unless explicit.
|
|
34
40
|
--help Show help.
|
|
35
41
|
```
|
|
36
42
|
|
|
43
|
+
## shadcn Presets And Semaphor Components
|
|
44
|
+
|
|
45
|
+
The starter ships with a working shadcn setup. For teams that bring their own
|
|
46
|
+
style, pass a shadcn preset during creation:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx create-semaphor-app@latest my-app --shadcn-preset <preset-id>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Semaphor UI helpers can be added from the public shadcn registry at scaffold
|
|
53
|
+
time:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx create-semaphor-app@latest my-app --components table
|
|
57
|
+
npx create-semaphor-app@latest my-app --components query-state,view-card,metric-kpis,filter-controls,server-data-table,matrix-table
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Component presets:
|
|
61
|
+
|
|
62
|
+
| Value | Installs |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `none` | No Semaphor registry components. This is the default. |
|
|
65
|
+
| `query` | `query-state` |
|
|
66
|
+
| `metrics` | `query-state-boundary`, `metric-kpis` |
|
|
67
|
+
| `filters` | `filter-controls` |
|
|
68
|
+
| `card` | `view-card` |
|
|
69
|
+
| `table` | `query-state`, `server-data-table` |
|
|
70
|
+
| `matrix` | `query-state`, `matrix-table` |
|
|
71
|
+
| `recommended` | `query-state`, `query-state-boundary`, `view-card`, `metric-kpis`, `filter-controls`, `server-data-table` |
|
|
72
|
+
| `all` | `query-state`, `query-state-boundary`, `view-card`, `metric-kpis`, `filter-controls`, `server-data-table`, `matrix-table` |
|
|
73
|
+
|
|
74
|
+
Registry components install as source into the generated app. They are optional
|
|
75
|
+
UI accelerators and do not replace `react-semaphor/data-app-sdk`.
|
|
76
|
+
|
|
77
|
+
`--shadcn-preset` and `--components` run the shadcn CLI, so they cannot be
|
|
78
|
+
combined with `--no-install`. To skip installs, scaffold the app first and run
|
|
79
|
+
the shadcn commands manually later.
|
|
80
|
+
|
|
37
81
|
## Local Validation
|
|
38
82
|
|
|
39
83
|
```bash
|
|
@@ -48,7 +92,7 @@ projects before exiting.
|
|
|
48
92
|
After creation:
|
|
49
93
|
|
|
50
94
|
```bash
|
|
51
|
-
cd
|
|
95
|
+
cd semaphor-data-app
|
|
52
96
|
npm run dev
|
|
53
97
|
```
|
|
54
98
|
|
|
@@ -10,8 +10,50 @@ import { fileURLToPath } from 'node:url';
|
|
|
10
10
|
const DEFAULT_TEMPLATE_REPO =
|
|
11
11
|
'https://github.com/semaphor-analytics/semaphor-data-app-starter.git';
|
|
12
12
|
const DEFAULT_TEMPLATE_REF = 'main';
|
|
13
|
+
const DEFAULT_APP_NAME = 'semaphor-data-app';
|
|
13
14
|
const MARKETPLACE = 'semaphor-analytics/agent-plugin';
|
|
14
15
|
const PLUGIN_ID = 'semaphor@semaphor-analytics';
|
|
16
|
+
const SEMAPHOR_COMPONENT_REGISTRY = 'semaphor-analytics/semaphor-data-app-components';
|
|
17
|
+
const SEMAPHOR_COMPONENT_PRESETS = Object.freeze({
|
|
18
|
+
none: [],
|
|
19
|
+
query: ['query-state'],
|
|
20
|
+
table: ['query-state', 'server-data-table'],
|
|
21
|
+
metrics: ['query-state-boundary', 'metric-kpis'],
|
|
22
|
+
filters: ['filter-controls'],
|
|
23
|
+
card: ['view-card'],
|
|
24
|
+
matrix: ['query-state', 'matrix-table'],
|
|
25
|
+
recommended: [
|
|
26
|
+
'query-state',
|
|
27
|
+
'query-state-boundary',
|
|
28
|
+
'view-card',
|
|
29
|
+
'metric-kpis',
|
|
30
|
+
'filter-controls',
|
|
31
|
+
'server-data-table',
|
|
32
|
+
],
|
|
33
|
+
all: [
|
|
34
|
+
'query-state',
|
|
35
|
+
'query-state-boundary',
|
|
36
|
+
'view-card',
|
|
37
|
+
'metric-kpis',
|
|
38
|
+
'filter-controls',
|
|
39
|
+
'server-data-table',
|
|
40
|
+
'matrix-table',
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
const SEMAPHOR_COMPONENT_ALIASES = Object.freeze({
|
|
44
|
+
'query-state': 'query-state',
|
|
45
|
+
'query-state-boundary': 'query-state-boundary',
|
|
46
|
+
'view-card': 'view-card',
|
|
47
|
+
card: 'view-card',
|
|
48
|
+
'server-data-table': 'server-data-table',
|
|
49
|
+
table: 'server-data-table',
|
|
50
|
+
'metric-kpis': 'metric-kpis',
|
|
51
|
+
kpis: 'metric-kpis',
|
|
52
|
+
'filter-controls': 'filter-controls',
|
|
53
|
+
filters: 'filter-controls',
|
|
54
|
+
'matrix-table': 'matrix-table',
|
|
55
|
+
matrix: 'matrix-table',
|
|
56
|
+
});
|
|
15
57
|
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
16
58
|
|
|
17
59
|
const cwd = process.cwd();
|
|
@@ -20,7 +62,7 @@ function usage() {
|
|
|
20
62
|
return `Create a Semaphor Data App starter project.
|
|
21
63
|
|
|
22
64
|
Usage:
|
|
23
|
-
npx create-semaphor-app@latest
|
|
65
|
+
npx create-semaphor-app@latest [app-name] [options]
|
|
24
66
|
|
|
25
67
|
Options:
|
|
26
68
|
--no-install Skip dependency installation.
|
|
@@ -30,6 +72,10 @@ Options:
|
|
|
30
72
|
--install-claude-plugin Install the Claude Code plugin without prompting.
|
|
31
73
|
--template <source> Starter source. Can be a local directory or git URL.
|
|
32
74
|
--template-ref <ref> Git branch/tag for the default starter repo.
|
|
75
|
+
--shadcn-preset <preset> Apply a shadcn preset before adding Semaphor components.
|
|
76
|
+
--shadcn-base <base> Pass base or radix to shadcn init when using a preset.
|
|
77
|
+
--components <list> Add Semaphor registry components. Use none, query, metrics,
|
|
78
|
+
filters, card, table, matrix, recommended, all, or a comma-separated list.
|
|
33
79
|
--yes, -y Use noninteractive defaults; skip optional plugin installs unless explicit.
|
|
34
80
|
--help, -h Show this help.
|
|
35
81
|
`;
|
|
@@ -45,6 +91,9 @@ function parseArgs(argv) {
|
|
|
45
91
|
installClaudePlugin: false,
|
|
46
92
|
template: DEFAULT_TEMPLATE_REPO,
|
|
47
93
|
templateRef: DEFAULT_TEMPLATE_REF,
|
|
94
|
+
shadcnPreset: null,
|
|
95
|
+
shadcnBase: null,
|
|
96
|
+
components: 'none',
|
|
48
97
|
yes: false,
|
|
49
98
|
help: false,
|
|
50
99
|
};
|
|
@@ -73,6 +122,18 @@ function parseArgs(argv) {
|
|
|
73
122
|
parsed.templateRef = readRequiredValue(argv, ++i, arg);
|
|
74
123
|
} else if (arg.startsWith('--template-ref=')) {
|
|
75
124
|
parsed.templateRef = arg.slice('--template-ref='.length);
|
|
125
|
+
} else if (arg === '--shadcn-preset') {
|
|
126
|
+
parsed.shadcnPreset = readRequiredValue(argv, ++i, arg);
|
|
127
|
+
} else if (arg.startsWith('--shadcn-preset=')) {
|
|
128
|
+
parsed.shadcnPreset = arg.slice('--shadcn-preset='.length);
|
|
129
|
+
} else if (arg === '--shadcn-base') {
|
|
130
|
+
parsed.shadcnBase = readRequiredValue(argv, ++i, arg);
|
|
131
|
+
} else if (arg.startsWith('--shadcn-base=')) {
|
|
132
|
+
parsed.shadcnBase = arg.slice('--shadcn-base='.length);
|
|
133
|
+
} else if (arg === '--components') {
|
|
134
|
+
parsed.components = readRequiredValue(argv, ++i, arg);
|
|
135
|
+
} else if (arg.startsWith('--components=')) {
|
|
136
|
+
parsed.components = arg.slice('--components='.length);
|
|
76
137
|
} else if (arg === '--yes' || arg === '-y') {
|
|
77
138
|
parsed.yes = true;
|
|
78
139
|
} else if (arg.startsWith('-')) {
|
|
@@ -224,6 +285,23 @@ function resolvePackageManager(requested) {
|
|
|
224
285
|
return 'npm';
|
|
225
286
|
}
|
|
226
287
|
|
|
288
|
+
function hasOwn(object, key) {
|
|
289
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function resolveShadcnRunner(packageManager) {
|
|
293
|
+
if (packageManager === 'pnpm') {
|
|
294
|
+
return { command: 'pnpm', args: ['dlx', 'shadcn@latest'] };
|
|
295
|
+
}
|
|
296
|
+
if (packageManager === 'yarn') {
|
|
297
|
+
return { command: 'yarn', args: ['dlx', 'shadcn@latest'] };
|
|
298
|
+
}
|
|
299
|
+
if (packageManager === 'bun') {
|
|
300
|
+
return { command: 'bunx', args: ['--bun', 'shadcn@latest'] };
|
|
301
|
+
}
|
|
302
|
+
return { command: 'npx', args: ['shadcn@latest'] };
|
|
303
|
+
}
|
|
304
|
+
|
|
227
305
|
function installArgs(packageManager) {
|
|
228
306
|
if (packageManager === 'yarn') return [];
|
|
229
307
|
return ['install'];
|
|
@@ -243,10 +321,7 @@ function promptQuestion(rl, question, defaultYes) {
|
|
|
243
321
|
|
|
244
322
|
async function resolveTarget(args, rl) {
|
|
245
323
|
if (!args.appName) {
|
|
246
|
-
|
|
247
|
-
throw new Error('App name is required.');
|
|
248
|
-
}
|
|
249
|
-
args.appName = await rl.question('App name: ');
|
|
324
|
+
args.appName = DEFAULT_APP_NAME;
|
|
250
325
|
}
|
|
251
326
|
|
|
252
327
|
const trimmed = String(args.appName || '').trim();
|
|
@@ -317,6 +392,107 @@ function loadTemplate(template, templateRef) {
|
|
|
317
392
|
return { tempRoot, templateDir };
|
|
318
393
|
}
|
|
319
394
|
|
|
395
|
+
function normalizeSemaphorComponents(value) {
|
|
396
|
+
const raw = String(value || 'none')
|
|
397
|
+
.split(',')
|
|
398
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
399
|
+
.filter(Boolean);
|
|
400
|
+
if (raw.length === 0) return [];
|
|
401
|
+
|
|
402
|
+
const components = [];
|
|
403
|
+
for (const entry of raw) {
|
|
404
|
+
if (hasOwn(SEMAPHOR_COMPONENT_PRESETS, entry)) {
|
|
405
|
+
components.push(...SEMAPHOR_COMPONENT_PRESETS[entry]);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (!hasOwn(SEMAPHOR_COMPONENT_ALIASES, entry)) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Unsupported Semaphor component "${entry}". Use none, query, metrics, filters, card, table, matrix, recommended, all, or one of ${Object.keys(SEMAPHOR_COMPONENT_ALIASES).join(', ')}.`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
components.push(SEMAPHOR_COMPONENT_ALIASES[entry]);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return Array.from(new Set(components));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function normalizeShadcnBase(value) {
|
|
420
|
+
if (!value) return null;
|
|
421
|
+
const normalized = String(value).trim().toLowerCase();
|
|
422
|
+
if (!['base', 'radix'].includes(normalized)) {
|
|
423
|
+
throw new Error('--shadcn-base must be base or radix.');
|
|
424
|
+
}
|
|
425
|
+
return normalized;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function validateScaffoldOptions(args) {
|
|
429
|
+
const components = normalizeSemaphorComponents(args.components);
|
|
430
|
+
normalizeShadcnBase(args.shadcnBase);
|
|
431
|
+
|
|
432
|
+
if (args.shadcnBase && !args.shadcnPreset) {
|
|
433
|
+
throw new Error('--shadcn-base requires --shadcn-preset.');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (args.shadcnPreset !== null && String(args.shadcnPreset).trim() === '') {
|
|
437
|
+
throw new Error('--shadcn-preset requires a non-empty preset id.');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!args.install && (args.shadcnPreset || components.length > 0)) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
'--no-install cannot be combined with --shadcn-preset or --components. Run create-semaphor-app with installs enabled, or add shadcn components manually after scaffolding.',
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function runShadcn(packageManager, targetDir, args) {
|
|
448
|
+
const runner = resolveShadcnRunner(packageManager);
|
|
449
|
+
ensureCommand(runner.command, runner.command);
|
|
450
|
+
runRequired(runner.command, [...runner.args, ...args], {
|
|
451
|
+
cwd: targetDir,
|
|
452
|
+
stdio: 'inherit',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function customizeShadcn(args, packageManager, targetDir) {
|
|
457
|
+
const components = normalizeSemaphorComponents(args.components);
|
|
458
|
+
const shadcnBase = normalizeShadcnBase(args.shadcnBase);
|
|
459
|
+
|
|
460
|
+
if (!args.shadcnPreset && components.length === 0) {
|
|
461
|
+
return { presetApplied: false, components };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (args.shadcnPreset) {
|
|
465
|
+
const initArgs = [
|
|
466
|
+
'init',
|
|
467
|
+
'--preset',
|
|
468
|
+
args.shadcnPreset,
|
|
469
|
+
'--template',
|
|
470
|
+
'vite',
|
|
471
|
+
'--force',
|
|
472
|
+
'--reinstall',
|
|
473
|
+
];
|
|
474
|
+
if (shadcnBase) {
|
|
475
|
+
initArgs.push('--base', shadcnBase);
|
|
476
|
+
}
|
|
477
|
+
console.log(`\nApplying shadcn preset ${args.shadcnPreset}...`);
|
|
478
|
+
runShadcn(packageManager, targetDir, initArgs);
|
|
479
|
+
console.log('✓ Applied shadcn preset');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (components.length > 0) {
|
|
483
|
+
console.log('\nAdding Semaphor registry components...');
|
|
484
|
+
for (const component of components) {
|
|
485
|
+
runShadcn(packageManager, targetDir, [
|
|
486
|
+
'add',
|
|
487
|
+
`${SEMAPHOR_COMPONENT_REGISTRY}/${component}`,
|
|
488
|
+
]);
|
|
489
|
+
console.log(`✓ Added ${component}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return { presetApplied: Boolean(args.shadcnPreset), components };
|
|
494
|
+
}
|
|
495
|
+
|
|
320
496
|
function detectAgents() {
|
|
321
497
|
return {
|
|
322
498
|
codex: commandExists('codex'),
|
|
@@ -413,12 +589,22 @@ async function maybeInstallPlugins(args, rl) {
|
|
|
413
589
|
return { detected, results, skipped: false };
|
|
414
590
|
}
|
|
415
591
|
|
|
416
|
-
function printNextSteps({ targetDir, packageManager, pluginSummary }) {
|
|
592
|
+
function printNextSteps({ targetDir, packageManager, pluginSummary, shadcnSummary }) {
|
|
417
593
|
const relativeTarget = path.relative(cwd, targetDir) || '.';
|
|
418
594
|
console.log('\nNext:');
|
|
419
595
|
console.log(` cd ${relativeTarget}`);
|
|
420
596
|
console.log(` ${packageManager} run dev`);
|
|
421
597
|
|
|
598
|
+
if (shadcnSummary?.presetApplied || shadcnSummary?.components?.length > 0) {
|
|
599
|
+
console.log('\nConfigured shadcn:');
|
|
600
|
+
if (shadcnSummary.presetApplied) {
|
|
601
|
+
console.log(' - Applied custom preset');
|
|
602
|
+
}
|
|
603
|
+
for (const component of shadcnSummary.components ?? []) {
|
|
604
|
+
console.log(` - Added Semaphor ${component}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
422
608
|
if (pluginSummary?.skipped) {
|
|
423
609
|
printPluginInstructions();
|
|
424
610
|
printBuildPrompt();
|
|
@@ -489,6 +675,14 @@ async function main() {
|
|
|
489
675
|
console.log(usage());
|
|
490
676
|
return;
|
|
491
677
|
}
|
|
678
|
+
try {
|
|
679
|
+
validateScaffoldOptions(args);
|
|
680
|
+
} catch (error) {
|
|
681
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
682
|
+
console.error('');
|
|
683
|
+
console.error(usage());
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
492
686
|
|
|
493
687
|
const rl = readline.createInterface({
|
|
494
688
|
input: process.stdin,
|
|
@@ -522,11 +716,13 @@ async function main() {
|
|
|
522
716
|
console.log('Skipping dependency installation.');
|
|
523
717
|
}
|
|
524
718
|
|
|
719
|
+
const shadcnSummary = customizeShadcn(args, packageManager, target.targetDir);
|
|
525
720
|
const pluginSummary = await maybeInstallPlugins(args, rl);
|
|
526
721
|
printNextSteps({
|
|
527
722
|
targetDir: target.targetDir,
|
|
528
723
|
packageManager,
|
|
529
724
|
pluginSummary,
|
|
725
|
+
shadcnSummary,
|
|
530
726
|
});
|
|
531
727
|
} catch (error) {
|
|
532
728
|
console.error('\nUnable to create Semaphor app.');
|
package/package.json
CHANGED
package/scripts/smoke-test.mjs
CHANGED
|
@@ -107,6 +107,20 @@ async function main() {
|
|
|
107
107
|
console.log('✓ local starter scaffold');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
{
|
|
111
|
+
const root = createTempRoot('default-name-');
|
|
112
|
+
const result = runCli([
|
|
113
|
+
'--template',
|
|
114
|
+
defaultLocalStarterPath,
|
|
115
|
+
'--no-install',
|
|
116
|
+
'--skip-plugin',
|
|
117
|
+
'--yes',
|
|
118
|
+
], { cwd: root });
|
|
119
|
+
assertSuccess(result, 'default app name scaffold');
|
|
120
|
+
assertScaffold(root, 'semaphor-data-app');
|
|
121
|
+
console.log('✓ default app name scaffold');
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
{
|
|
111
125
|
const root = createTempRoot('github-');
|
|
112
126
|
const result = runCli([
|
|
@@ -154,6 +168,100 @@ async function main() {
|
|
|
154
168
|
console.log('✓ fake Codex plugin install path');
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
{
|
|
172
|
+
const root = createTempRoot('shadcn-');
|
|
173
|
+
const binDir = path.join(root, 'bin');
|
|
174
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
175
|
+
const shadcnLog = path.join(root, 'shadcn.log');
|
|
176
|
+
const npmLog = path.join(root, 'npm.log');
|
|
177
|
+
createFakeCommand(binDir, 'npx', shadcnLog);
|
|
178
|
+
createFakeCommand(binDir, 'npm', npmLog);
|
|
179
|
+
|
|
180
|
+
const result = runCli([
|
|
181
|
+
'styled-app',
|
|
182
|
+
'--template',
|
|
183
|
+
defaultLocalStarterPath,
|
|
184
|
+
'--skip-plugin',
|
|
185
|
+
'--yes',
|
|
186
|
+
'--package-manager',
|
|
187
|
+
'npm',
|
|
188
|
+
'--shadcn-preset',
|
|
189
|
+
'test-preset',
|
|
190
|
+
'--shadcn-base',
|
|
191
|
+
'base',
|
|
192
|
+
'--components',
|
|
193
|
+
'recommended,matrix-table',
|
|
194
|
+
], {
|
|
195
|
+
cwd: root,
|
|
196
|
+
env: {
|
|
197
|
+
...process.env,
|
|
198
|
+
PATH: `${binDir}${path.delimiter}${process.env.PATH || ''}`,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
assertSuccess(result, 'shadcn preset and component install');
|
|
202
|
+
assertScaffold(root, 'styled-app');
|
|
203
|
+
const npmInstallLog = fs.readFileSync(npmLog, 'utf8');
|
|
204
|
+
assert(npmInstallLog.includes('npm install'), 'expected npm install command');
|
|
205
|
+
const log = fs.readFileSync(shadcnLog, 'utf8');
|
|
206
|
+
assert(
|
|
207
|
+
log.includes('npx shadcn@latest init --preset test-preset --template vite --force --reinstall --base base'),
|
|
208
|
+
'expected shadcn preset init command',
|
|
209
|
+
);
|
|
210
|
+
assert(
|
|
211
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/query-state'),
|
|
212
|
+
'expected query-state registry add command',
|
|
213
|
+
);
|
|
214
|
+
assert(
|
|
215
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/query-state-boundary'),
|
|
216
|
+
'expected query-state-boundary registry add command',
|
|
217
|
+
);
|
|
218
|
+
assert(
|
|
219
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/view-card'),
|
|
220
|
+
'expected view-card registry add command',
|
|
221
|
+
);
|
|
222
|
+
assert(
|
|
223
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/metric-kpis'),
|
|
224
|
+
'expected metric-kpis registry add command',
|
|
225
|
+
);
|
|
226
|
+
assert(
|
|
227
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/filter-controls'),
|
|
228
|
+
'expected filter-controls registry add command',
|
|
229
|
+
);
|
|
230
|
+
assert(
|
|
231
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/server-data-table'),
|
|
232
|
+
'expected server-data-table registry add command',
|
|
233
|
+
);
|
|
234
|
+
assert(
|
|
235
|
+
log.includes('npx shadcn@latest add semaphor-analytics/semaphor-data-app-components/matrix-table'),
|
|
236
|
+
'expected matrix-table registry add command',
|
|
237
|
+
);
|
|
238
|
+
console.log('✓ shadcn preset and registry component path');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
{
|
|
242
|
+
const root = createTempRoot('no-install-shadcn-');
|
|
243
|
+
const result = runCli([
|
|
244
|
+
'invalid-shadcn-app',
|
|
245
|
+
'--template',
|
|
246
|
+
defaultLocalStarterPath,
|
|
247
|
+
'--no-install',
|
|
248
|
+
'--skip-plugin',
|
|
249
|
+
'--yes',
|
|
250
|
+
'--components',
|
|
251
|
+
'table',
|
|
252
|
+
], { cwd: root });
|
|
253
|
+
assert(result.status !== 0, 'expected shadcn options with --no-install to fail');
|
|
254
|
+
assert(
|
|
255
|
+
result.stderr.includes('--no-install cannot be combined'),
|
|
256
|
+
'expected actionable --no-install error',
|
|
257
|
+
);
|
|
258
|
+
assert(
|
|
259
|
+
!fs.existsSync(path.join(root, 'invalid-shadcn-app')),
|
|
260
|
+
'invalid shadcn options should fail before scaffolding',
|
|
261
|
+
);
|
|
262
|
+
console.log('✓ rejects shadcn customization with --no-install');
|
|
263
|
+
}
|
|
264
|
+
|
|
157
265
|
console.log('✓ cleaned up temp projects');
|
|
158
266
|
} finally {
|
|
159
267
|
for (const root of tempRoots.reverse()) {
|
|
@@ -163,4 +271,3 @@ async function main() {
|
|
|
163
271
|
}
|
|
164
272
|
|
|
165
273
|
await main();
|
|
166
|
-
|