@astryxdesign/cli 0.1.7-canary.fb9c68c → 0.1.7
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 +19 -115
- package/docs/getting-started.doc.mjs +9 -9
- package/docs/integration-authoring.md +105 -0
- package/docs/migration.doc.mjs +18 -18
- package/docs/principles.doc.dense.mjs +1 -1
- package/docs/principles.doc.mjs +6 -6
- package/docs/principles.doc.zh.mjs +1 -1
- package/docs/styling-libraries.doc.mjs +3 -3
- package/docs/styling.doc.mjs +4 -4
- package/docs/theme.doc.dense.mjs +2 -2
- package/docs/theme.doc.mjs +7 -7
- package/docs/theme.doc.zh.mjs +1 -1
- package/docs/tokens.doc.mjs +1 -1
- package/docs/working-with-ai.doc.mjs +18 -18
- package/package.json +10 -11
- package/src/api/doctor.mjs +3 -3
- package/src/codemods/ensure-jscodeshift.mjs +27 -11
- package/src/codemods/run-codemod.mjs +1 -1
- package/src/codemods/runner.mjs +2 -2
- package/src/commands/agent-docs.mjs +14 -56
- package/src/commands/agent-docs.test.mjs +4 -11
- package/src/commands/build-theme.mjs +71 -10
- package/src/commands/build.mjs +15 -15
- package/src/commands/component/index.mjs +4 -4
- package/src/commands/discover.mjs +5 -7
- package/src/commands/docs.mjs +4 -4
- package/src/commands/hook/index.mjs +4 -4
- package/src/commands/init.mjs +152 -48
- package/src/commands/init.next-steps.test.mjs +1 -1
- package/src/commands/interactive-guard.test.mjs +22 -19
- package/src/commands/json-contract.test.mjs +1 -1
- package/src/commands/layout.mjs +1 -1
- package/src/commands/search.mjs +4 -4
- package/src/commands/swizzle.mjs +34 -11
- package/src/commands/template.mjs +31 -11
- package/src/commands/upgrade.mjs +6 -9
- package/src/commands/upgrade.test.mjs +1 -1
- package/src/index.mjs +6 -40
- package/src/lib/component-format.mjs +1 -2
- package/src/utils/interactive.mjs +76 -0
- package/src/utils/interactive.test.mjs +70 -0
- package/src/utils/package-manager.mjs +0 -78
- package/src/utils/package-manager.test.mjs +1 -108
- package/src/utils/path-safety.mjs +18 -0
- package/src/utils/update-check.mjs +1 -2
- package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
- package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +7 -2
- package/docs/cli-integrations.doc.mjs +0 -150
- package/scripts/postinstall.mjs +0 -74
- package/src/commands/cli-postinstall.test.mjs +0 -42
- package/src/commands/setup-nudge.test.mjs +0 -108
- package/src/lib/term-log.mjs +0 -48
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
formatBriefAll,
|
|
19
19
|
} from '../../lib/component-format.mjs';
|
|
20
20
|
import {resolveTheme} from '../../lib/resolve-theme.mjs';
|
|
21
|
-
import {
|
|
21
|
+
import {getRunPrefix} from '../../utils/package-manager.mjs';
|
|
22
22
|
import {jsonOut, humanLog} from '../../lib/json.mjs';
|
|
23
23
|
import {cliError} from '../../lib/cli-error.mjs';
|
|
24
24
|
import {ERROR_CODES} from '../../lib/error-codes.mjs';
|
|
@@ -39,7 +39,7 @@ export function registerComponent(program) {
|
|
|
39
39
|
.option('--blocks', 'List example blocks: showcase, examples, and related')
|
|
40
40
|
.option('--package <name>', 'Scope lookup to an external package (e.g. @acme/xds-widgets)')
|
|
41
41
|
.action(async (name, options) => {
|
|
42
|
-
const run =
|
|
42
|
+
const run = getRunPrefix();
|
|
43
43
|
const zh = program.opts().zh || false;
|
|
44
44
|
const dense = program.opts().dense || false;
|
|
45
45
|
const lang = program.opts().lang || null;
|
|
@@ -142,7 +142,7 @@ export function registerComponent(program) {
|
|
|
142
142
|
}
|
|
143
143
|
humanLog('');
|
|
144
144
|
humanLog(`Import from the path shown (e.g. import {Button} from '@astryxdesign/core/Button')`);
|
|
145
|
-
humanLog(`Usage: ${run} component <name>`);
|
|
145
|
+
humanLog(`Usage: ${run} astryx component <name>`);
|
|
146
146
|
humanLog('');
|
|
147
147
|
}
|
|
148
148
|
break;
|
|
@@ -165,7 +165,7 @@ export function registerComponent(program) {
|
|
|
165
165
|
humanLog('');
|
|
166
166
|
}
|
|
167
167
|
humanLog(`Import from the path shown (e.g. import {Button} from '@astryxdesign/core/Button')`);
|
|
168
|
-
humanLog(`Usage: ${run} component <name>`);
|
|
168
|
+
humanLog(`Usage: ${run} astryx component <name>`);
|
|
169
169
|
humanLog('');
|
|
170
170
|
break;
|
|
171
171
|
}
|
|
@@ -14,7 +14,6 @@ import {formatFull, formatBrief, formatCompact} from '../lib/component-format.mj
|
|
|
14
14
|
import {jsonOut, humanLog} from '../lib/json.mjs';
|
|
15
15
|
import {cliError} from '../lib/cli-error.mjs';
|
|
16
16
|
import {discover as discoverApi} from '../api/discover.mjs';
|
|
17
|
-
import {getCliInvocation} from '../utils/package-manager.mjs';
|
|
18
17
|
|
|
19
18
|
export function registerDiscover(program) {
|
|
20
19
|
program
|
|
@@ -26,7 +25,6 @@ export function registerDiscover(program) {
|
|
|
26
25
|
const json = program.opts().json || false;
|
|
27
26
|
const lang = program.opts().lang || null;
|
|
28
27
|
const zh = program.opts().zh || false;
|
|
29
|
-
const run = getCliInvocation();
|
|
30
28
|
|
|
31
29
|
let result;
|
|
32
30
|
try {
|
|
@@ -82,9 +80,9 @@ export function registerDiscover(program) {
|
|
|
82
80
|
humanLog('');
|
|
83
81
|
}
|
|
84
82
|
humanLog('Usage:');
|
|
85
|
-
humanLog(
|
|
86
|
-
humanLog(
|
|
87
|
-
humanLog(
|
|
83
|
+
humanLog(' astryx discover <package> Browse a package');
|
|
84
|
+
humanLog(' astryx discover <package>/Component View component docs');
|
|
85
|
+
humanLog(' astryx discover <search> Search all packages');
|
|
88
86
|
humanLog('');
|
|
89
87
|
}
|
|
90
88
|
break;
|
|
@@ -101,7 +99,7 @@ export function registerDiscover(program) {
|
|
|
101
99
|
humanLog('');
|
|
102
100
|
for (const comp of result.data.components) humanLog(' ' + comp);
|
|
103
101
|
humanLog('');
|
|
104
|
-
humanLog(
|
|
102
|
+
humanLog('Usage: astryx discover ' + result.data.name + '/<ComponentName>');
|
|
105
103
|
humanLog('');
|
|
106
104
|
break;
|
|
107
105
|
}
|
|
@@ -124,7 +122,7 @@ export function registerDiscover(program) {
|
|
|
124
122
|
humanLog('Found ' + result.data.matches.length + ' matches for "' + result.data.query + '":');
|
|
125
123
|
humanLog('');
|
|
126
124
|
for (const m of result.data.matches) {
|
|
127
|
-
humanLog(
|
|
125
|
+
humanLog(' astryx discover ' + m.package + '/' + m.component);
|
|
128
126
|
}
|
|
129
127
|
humanLog('');
|
|
130
128
|
break;
|
package/src/commands/docs.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* astryx docs <topic> <section> Print one section
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
16
16
|
import {jsonOut, humanLog} from '../lib/json.mjs';
|
|
17
17
|
import {cliError} from '../lib/cli-error.mjs';
|
|
18
18
|
import {docs as docsApi} from '../api/docs.mjs';
|
|
@@ -105,7 +105,7 @@ export function registerDocs(program) {
|
|
|
105
105
|
.command('docs [topic] [section]')
|
|
106
106
|
.description('Print reference docs')
|
|
107
107
|
.action(async (topic, section) => {
|
|
108
|
-
const run =
|
|
108
|
+
const run = getRunPrefix();
|
|
109
109
|
const lang = program.opts().lang || null;
|
|
110
110
|
const zh = program.opts().zh || false;
|
|
111
111
|
const dense = program.opts().dense || false;
|
|
@@ -130,8 +130,8 @@ export function registerDocs(program) {
|
|
|
130
130
|
for (const entry of result.data) {
|
|
131
131
|
humanLog(` ${entry.topic.padEnd(14)} ${entry.description}`);
|
|
132
132
|
}
|
|
133
|
-
humanLog(`\nUsage: ${run} docs <topic>`);
|
|
134
|
-
humanLog(` ${run} docs <topic> <section>\n`);
|
|
133
|
+
humanLog(`\nUsage: ${run} astryx docs <topic>`);
|
|
134
|
+
humanLog(` ${run} astryx docs <topic> <section>\n`);
|
|
135
135
|
break;
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
formatHookBrief,
|
|
13
13
|
formatHookParams,
|
|
14
14
|
} from '../../lib/hook-format.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {getRunPrefix} from '../../utils/package-manager.mjs';
|
|
16
16
|
import {jsonOut, humanLog} from '../../lib/json.mjs';
|
|
17
17
|
import {cliError} from '../../lib/cli-error.mjs';
|
|
18
18
|
import {ERROR_CODES} from '../../lib/error-codes.mjs';
|
|
@@ -27,7 +27,7 @@ export function registerHook(program) {
|
|
|
27
27
|
.option('--category <category>', 'List hooks in a specific category')
|
|
28
28
|
.option('--params', 'Print only the parameters table')
|
|
29
29
|
.action(async (name, options) => {
|
|
30
|
-
const run =
|
|
30
|
+
const run = getRunPrefix();
|
|
31
31
|
const zh = program.opts().zh || false;
|
|
32
32
|
const lang = program.opts().lang || null;
|
|
33
33
|
const detailSource = program.getOptionValueSource('detail');
|
|
@@ -76,7 +76,7 @@ export function registerHook(program) {
|
|
|
76
76
|
for (const h of hookNames) humanLog(` ${h}`);
|
|
77
77
|
}
|
|
78
78
|
humanLog('');
|
|
79
|
-
humanLog(`Usage: ${run} hook <name>`);
|
|
79
|
+
humanLog(`Usage: ${run} astryx hook <name>`);
|
|
80
80
|
humanLog('');
|
|
81
81
|
}
|
|
82
82
|
break;
|
|
@@ -93,7 +93,7 @@ export function registerHook(program) {
|
|
|
93
93
|
}
|
|
94
94
|
humanLog('');
|
|
95
95
|
}
|
|
96
|
-
humanLog(`Usage: ${run} hook <name>`);
|
|
96
|
+
humanLog(`Usage: ${run} astryx hook <name>`);
|
|
97
97
|
humanLog('');
|
|
98
98
|
break;
|
|
99
99
|
}
|
package/src/commands/init.mjs
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file init command —
|
|
4
|
+
* @file init command — Interactive initialization wizard + feature installer
|
|
5
5
|
*
|
|
6
|
-
* `astryx init`
|
|
7
|
-
*
|
|
8
|
-
* AI agents, CI, and piped I/O — it never hangs or errors on a missing TTY.
|
|
9
|
-
* Non-interactive feature install: `astryx init --features agents,theme,template`
|
|
6
|
+
* Interactive: `astryx init` walks through all features
|
|
7
|
+
* Non-interactive: `astryx init --features agents,theme,template`
|
|
10
8
|
* Re-runnable: safe to run multiple times, idempotent
|
|
11
9
|
*
|
|
12
10
|
* Features:
|
|
13
11
|
* agents — Install AGENTS.md/CLAUDE.md cheat sheet for AI coding agents
|
|
14
|
-
* theme —
|
|
12
|
+
* theme — Scaffold a custom theme file
|
|
15
13
|
* template — Copy a starter page template
|
|
16
14
|
*/
|
|
17
15
|
|
|
16
|
+
import * as p from '@clack/prompts';
|
|
18
17
|
import * as path from 'node:path';
|
|
19
18
|
import * as fs from 'node:fs';
|
|
20
19
|
import {CLI_ROOT} from '../utils/paths.mjs';
|
|
21
20
|
import {PathSafetyError} from '../utils/path-safety.mjs';
|
|
22
|
-
import {
|
|
21
|
+
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
23
22
|
import {installAgentDocs, removeAgentDocs} from './agent-docs.mjs';
|
|
24
23
|
import {listTemplates} from './template.mjs';
|
|
25
24
|
import {humanLog} from '../lib/json.mjs';
|
|
26
25
|
import {cliError} from '../lib/cli-error.mjs';
|
|
27
26
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
27
|
+
import {requireInteractive} from '../utils/interactive.mjs';
|
|
28
28
|
|
|
29
29
|
const VALID_FEATURES = ['agents', 'theme', 'template'];
|
|
30
|
-
const run =
|
|
30
|
+
const run = getRunPrefix();
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Build the "Next steps" lines printed at the end of `astryx init`.
|
|
@@ -40,10 +40,10 @@ const run = getCliInvocation();
|
|
|
40
40
|
*
|
|
41
41
|
* Exported for testing.
|
|
42
42
|
*
|
|
43
|
-
* @param {string}
|
|
43
|
+
* @param {string} runPrefix package-manager run prefix (e.g. `npx`)
|
|
44
44
|
* @returns {string[]} ordered list of human-facing lines
|
|
45
45
|
*/
|
|
46
|
-
export function getNextSteps(
|
|
46
|
+
export function getNextSteps(runPrefix) {
|
|
47
47
|
return [
|
|
48
48
|
'',
|
|
49
49
|
' Next steps:',
|
|
@@ -54,15 +54,23 @@ export function getNextSteps(invocation) {
|
|
|
54
54
|
" import { neutralTheme } from '@astryxdesign/theme-neutral/built'",
|
|
55
55
|
" import '@astryxdesign/theme-neutral/theme.css'",
|
|
56
56
|
' <Theme theme={neutralTheme}>...</Theme>',
|
|
57
|
-
` For custom themes, run \`${
|
|
58
|
-
` 4. ${
|
|
57
|
+
` For custom themes, run \`${runPrefix} astryx theme build <file>\` to generate the built artifacts.`,
|
|
58
|
+
` 4. ${runPrefix} astryx --help for all commands`,
|
|
59
59
|
'',
|
|
60
60
|
];
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function isCancel(value) {
|
|
64
|
+
if (p.isCancel(value)) {
|
|
65
|
+
p.cancel('Setup cancelled.');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
// ─── Feature: agents ─────────────────────────────────────────────────────────
|
|
64
72
|
|
|
65
|
-
function runAgents(targetDir, {agent, agentDocsPath} = {}) {
|
|
73
|
+
function runAgents(targetDir, {interactive = true, agent, agentDocsPath} = {}) {
|
|
66
74
|
try {
|
|
67
75
|
const paths = agentDocsPath
|
|
68
76
|
? Array.isArray(agentDocsPath)
|
|
@@ -71,55 +79,114 @@ function runAgents(targetDir, {agent, agentDocsPath} = {}) {
|
|
|
71
79
|
: undefined;
|
|
72
80
|
const written = installAgentDocs(targetDir, {agent, paths});
|
|
73
81
|
const summary = written.join(', ');
|
|
74
|
-
|
|
82
|
+
if (interactive) {
|
|
83
|
+
p.log.success(`AI agent docs installed → ${summary}`);
|
|
84
|
+
} else {
|
|
85
|
+
humanLog(`✓ AI agent docs installed → ${summary}`);
|
|
86
|
+
}
|
|
75
87
|
} catch (err) {
|
|
76
88
|
// PathSafetyError carries a precise, user-actionable message —
|
|
77
89
|
// surface it instead of the generic "could not install" warning so
|
|
78
90
|
// misconfigured --agent-docs-path values aren't silently swallowed.
|
|
79
91
|
if (err instanceof PathSafetyError) {
|
|
80
|
-
|
|
92
|
+
const msg = `Error: ${err.message}`;
|
|
93
|
+
if (interactive) {
|
|
94
|
+
p.log.error(msg);
|
|
95
|
+
} else {
|
|
96
|
+
console.error(msg);
|
|
97
|
+
}
|
|
81
98
|
process.exitCode = 1;
|
|
82
99
|
return;
|
|
83
100
|
}
|
|
84
|
-
|
|
101
|
+
const msg = `Could not install agent docs. Try again with \`${run} astryx init --features agents\`.`;
|
|
102
|
+
if (interactive) {
|
|
103
|
+
p.log.warning(msg);
|
|
104
|
+
} else {
|
|
105
|
+
console.error(msg);
|
|
106
|
+
}
|
|
85
107
|
}
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
// ─── Feature: theme ──────────────────────────────────────────────────────────
|
|
89
111
|
|
|
90
|
-
function runTheme() {
|
|
91
|
-
|
|
112
|
+
async function runTheme({interactive = true} = {}) {
|
|
113
|
+
if (!interactive) {
|
|
114
|
+
humanLog(`✓ Theme scaffolding requires interactive mode. Run \`${run} astryx theme\` instead.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
p.note(
|
|
119
|
+
'Create a custom theme with your brand colors.\n' +
|
|
120
|
+
`Run \`${run} astryx theme\` for the full theme wizard.\n` +
|
|
121
|
+
`Run \`${run} astryx theme --list\` to see existing themes.`,
|
|
122
|
+
'Themes',
|
|
123
|
+
);
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
// ─── Feature: template ───────────────────────────────────────────────────────
|
|
95
127
|
|
|
96
|
-
function runTemplate(targetDir, {templateName} = {}) {
|
|
128
|
+
async function runTemplate(targetDir, {interactive = true, templateName} = {}) {
|
|
97
129
|
const templates = listTemplates();
|
|
98
130
|
if (templates.length === 0) return;
|
|
99
131
|
|
|
100
|
-
if (!
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
if (!interactive) {
|
|
133
|
+
if (!templateName) {
|
|
134
|
+
// Point agents at the build workflow rather than dumping page-template
|
|
135
|
+
// names — `build` surfaces pages AND blocks AND components for an idea,
|
|
136
|
+
// and `build` with no args is the full how-to-build playbook.
|
|
137
|
+
humanLog('✓ To build UI, use these commands:');
|
|
138
|
+
humanLog('');
|
|
139
|
+
humanLog(` ${run} astryx build "<what you're building>" build a page — kit: closest template + blocks + components`);
|
|
140
|
+
humanLog(` ${run} astryx build the how-to-build workflow (read this first)`);
|
|
141
|
+
humanLog(` ${run} astryx search <query> find anything — components, docs, templates, blocks`);
|
|
142
|
+
humanLog('');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!templates.includes(templateName)) {
|
|
147
|
+
cliError(`Unknown template "${templateName}". Available: ${templates.join(', ')}`, {code: ERROR_CODES.ERR_UNKNOWN_TEMPLATE});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
112
150
|
|
|
113
|
-
|
|
114
|
-
|
|
151
|
+
const outputDir = path.resolve(targetDir, `./src/pages/${templateName}`);
|
|
152
|
+
const srcPath = path.join(CLI_ROOT, 'templates', 'pages', templateName, 'page.tsx');
|
|
153
|
+
fs.mkdirSync(outputDir, {recursive: true});
|
|
154
|
+
fs.copyFileSync(srcPath, path.join(outputDir, 'page.tsx'));
|
|
155
|
+
humanLog(`✓ Template created at ${path.relative(targetDir, outputDir)}/page.tsx`);
|
|
115
156
|
return;
|
|
116
157
|
}
|
|
117
158
|
|
|
118
|
-
const
|
|
119
|
-
|
|
159
|
+
const TEMPLATE_OPTIONS = [
|
|
160
|
+
{value: 'skip', label: 'Skip — No template'},
|
|
161
|
+
{value: 'blank', label: 'Blank Page — Minimal scaffold'},
|
|
162
|
+
{value: 'table', label: 'Table Page — Data table with actions'},
|
|
163
|
+
{value: 'login', label: 'Login Page — Auth form with inputs'},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const templateChoice = isCancel(
|
|
167
|
+
await p.select({
|
|
168
|
+
message: 'Start with a page template?',
|
|
169
|
+
options: TEMPLATE_OPTIONS,
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (templateChoice === 'skip') return;
|
|
174
|
+
|
|
175
|
+
const targetPath = isCancel(
|
|
176
|
+
await p.text({
|
|
177
|
+
message: 'Where should the template be created?',
|
|
178
|
+
initialValue: `./src/pages/${templateChoice}`,
|
|
179
|
+
placeholder: `./src/pages/${templateChoice}`,
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const outputDir = path.resolve(targetDir, targetPath);
|
|
184
|
+
const srcPath = path.join(CLI_ROOT, 'templates', 'pages', templateChoice, 'page.tsx');
|
|
185
|
+
|
|
120
186
|
fs.mkdirSync(outputDir, {recursive: true});
|
|
121
187
|
fs.copyFileSync(srcPath, path.join(outputDir, 'page.tsx'));
|
|
122
|
-
|
|
188
|
+
|
|
189
|
+
p.log.success(`Template created at ${path.relative(targetDir, outputDir)}/page.tsx`);
|
|
123
190
|
}
|
|
124
191
|
|
|
125
192
|
// ─── Command ─────────────────────────────────────────────────────────────────
|
|
@@ -133,7 +200,7 @@ export function registerInit(program) {
|
|
|
133
200
|
.option('--remove-agents', 'Remove AI agent docs from all agent doc files')
|
|
134
201
|
.option('--agent <tool>', 'Target AI tool for agent docs: claude, cursor, codex, hermes, all')
|
|
135
202
|
.option('--agent-docs-path <path...>', 'Explicit file path(s) for agent docs')
|
|
136
|
-
.action((options) => {
|
|
203
|
+
.action(async (options) => {
|
|
137
204
|
const targetDir = process.cwd();
|
|
138
205
|
|
|
139
206
|
// Remove mode
|
|
@@ -157,25 +224,62 @@ export function registerInit(program) {
|
|
|
157
224
|
|
|
158
225
|
for (const feature of features) {
|
|
159
226
|
if (feature === 'agents') runAgents(targetDir, {
|
|
227
|
+
interactive: false,
|
|
160
228
|
agent: options.agent,
|
|
161
229
|
agentDocsPath: options.agentDocsPath,
|
|
162
230
|
});
|
|
163
|
-
if (feature === 'theme') runTheme();
|
|
164
|
-
if (feature === 'template') runTemplate(targetDir, {});
|
|
231
|
+
if (feature === 'theme') await runTheme({interactive: false});
|
|
232
|
+
if (feature === 'template') await runTemplate(targetDir, {interactive: false});
|
|
165
233
|
}
|
|
166
234
|
return;
|
|
167
235
|
}
|
|
168
236
|
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
// Interactive wizard
|
|
238
|
+
//
|
|
239
|
+
// Guard: this wizard blocks on prompts. In a non-interactive context
|
|
240
|
+
// (CI, piped stdin/stdout, no TTY) it would hang forever. Fail fast
|
|
241
|
+
// with actionable guidance via the shared interactivity contract.
|
|
242
|
+
requireInteractive({
|
|
243
|
+
command: 'init',
|
|
244
|
+
hint: `\`${run} astryx init --all\` or \`--features agents,theme,template\``,
|
|
176
245
|
});
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
|
|
247
|
+
p.intro('Welcome to the design system');
|
|
248
|
+
|
|
249
|
+
p.note(
|
|
250
|
+
'A design system for building internal tools\nwith 300+ React components.',
|
|
251
|
+
'About',
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Feature: agents
|
|
255
|
+
const shouldInstallAgents = isCancel(
|
|
256
|
+
await p.confirm({
|
|
257
|
+
message: 'Install AI agent support? (adds a design system cheat sheet to AGENTS.md)',
|
|
258
|
+
initialValue: true,
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (shouldInstallAgents) {
|
|
263
|
+
const s = p.spinner();
|
|
264
|
+
s.start('Installing agent docs');
|
|
265
|
+
runAgents(targetDir);
|
|
266
|
+
s.stop('Done');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Feature: swizzle awareness
|
|
270
|
+
p.note(
|
|
271
|
+
`You can customize any component with:\n ${run} astryx swizzle Button\n ${run} astryx swizzle --list`,
|
|
272
|
+
'Component Customization',
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Feature: template
|
|
276
|
+
await runTemplate(targetDir);
|
|
277
|
+
|
|
278
|
+
// Feature: theme awareness
|
|
279
|
+
await runTheme();
|
|
280
|
+
|
|
281
|
+
// Outro
|
|
282
|
+
p.outro('Design system initialized!');
|
|
179
283
|
|
|
180
284
|
for (const line of getNextSteps(run)) {
|
|
181
285
|
humanLog(line);
|
|
@@ -23,7 +23,7 @@ import {describe, it, expect} from 'vitest';
|
|
|
23
23
|
import {getNextSteps} from './init.mjs';
|
|
24
24
|
|
|
25
25
|
describe('init Next steps theme guidance', () => {
|
|
26
|
-
const text = getNextSteps('npx
|
|
26
|
+
const text = getNextSteps('npx').join('\n');
|
|
27
27
|
|
|
28
28
|
it('mentions the base CSS imports so the app is not left unstyled', () => {
|
|
29
29
|
expect(text).toContain("'@astryxdesign/core/reset.css'");
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file Subprocess no-hang tests for
|
|
4
|
+
* @file Subprocess no-hang tests for the interactivity contract.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* /
|
|
10
|
-
*
|
|
6
|
+
* The wizard command `init` blocks on @clack/prompts. In a
|
|
7
|
+
* non-interactive context it must fail fast (exit 1) instead of hanging.
|
|
8
|
+
* These tests spawn the CLI as a real subprocess with stdin/stdout NOT a TTY
|
|
9
|
+
* (stdio 'ignore'/'pipe'), which is exactly the CI / piped condition. If the
|
|
10
|
+
* guard were missing, spawnSync would hit the timeout (signal SIGTERM,
|
|
11
|
+
* status null) — so asserting `signal === null && status === 1` proves the
|
|
12
|
+
* process exited cleanly rather than hanging.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
@@ -40,24 +42,25 @@ afterEach(() => {
|
|
|
40
42
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
describe('init
|
|
44
|
-
it('
|
|
45
|
+
describe('init non-interactive safety', () => {
|
|
46
|
+
it('fails fast (exit 1, no hang) and writes no files', () => {
|
|
45
47
|
const r = runCli(['init']);
|
|
46
|
-
expect(r.signal).toBeNull();
|
|
47
|
-
expect(r.status).toBe(
|
|
48
|
-
expect(fs.readdirSync(tmpDir)
|
|
48
|
+
expect(r.signal).toBeNull();
|
|
49
|
+
expect(r.status).toBe(1);
|
|
50
|
+
expect(fs.readdirSync(tmpDir)).toEqual([]);
|
|
49
51
|
});
|
|
50
52
|
|
|
51
|
-
it('
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
53
|
+
it('prints actionable guidance (--all / --features)', () => {
|
|
54
|
+
const out = (() => {
|
|
55
|
+
const r = runCli(['init']);
|
|
56
|
+
return r.stderr + r.stdout;
|
|
57
|
+
})();
|
|
58
|
+
expect(out).toMatch(/requires a TTY/i);
|
|
59
|
+
expect(out).toMatch(/--all/);
|
|
60
|
+
expect(out).toMatch(/--features/);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
|
-
it('still runs --features agents non-interactively', () => {
|
|
63
|
+
it('still runs --features agents non-interactively (guard does not over-catch)', () => {
|
|
61
64
|
const r = runCli(['init', '--features', 'agents']);
|
|
62
65
|
expect(r.signal).toBeNull();
|
|
63
66
|
expect(r.status).toBe(0);
|
|
@@ -45,7 +45,7 @@ function runCli(args, {cwd} = {}) {
|
|
|
45
45
|
|
|
46
46
|
function parseJson(stdout) {
|
|
47
47
|
// CLI emits a single JSON document. If anything else snuck onto stdout
|
|
48
|
-
// (
|
|
48
|
+
// (clack output, console.log strings, etc.) JSON.parse will throw —
|
|
49
49
|
// which is exactly the failure mode we want to catch in tests.
|
|
50
50
|
return JSON.parse(stdout);
|
|
51
51
|
}
|
package/src/commands/layout.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import {layoutExpand, layoutCheck, layoutGrammar} from '../api/layout.mjs';
|
|
|
20
20
|
/** Resolve the expression from arg, --file, or stdin ('-'). */
|
|
21
21
|
async function readExpression(expr, options) {
|
|
22
22
|
if (options.file) return fs.readFileSync(options.file, 'utf-8');
|
|
23
|
-
if (expr === '-') {
|
|
23
|
+
if (expr === '-' || (!expr && !process.stdin.isTTY)) {
|
|
24
24
|
const chunks = [];
|
|
25
25
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
26
26
|
return Buffer.concat(chunks).toString('utf-8');
|
package/src/commands/search.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* astryx search button --json Typed JSON envelope
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
20
20
|
import {jsonOut, humanLog} from '../lib/json.mjs';
|
|
21
21
|
import {cliError} from '../lib/cli-error.mjs';
|
|
22
22
|
import {search as searchApi, SEARCH_DOMAINS} from '../api/search.mjs';
|
|
@@ -63,7 +63,7 @@ export function registerSearch(program) {
|
|
|
63
63
|
if (json) return jsonOut(result.type, result.data);
|
|
64
64
|
|
|
65
65
|
// ── Text output ──────────────────────────────────────────────
|
|
66
|
-
const run =
|
|
66
|
+
const run = getRunPrefix();
|
|
67
67
|
const {query: q, results} = result.data;
|
|
68
68
|
|
|
69
69
|
// No matches is a valid, successful outcome — clean message, exit 0.
|
|
@@ -71,7 +71,7 @@ export function registerSearch(program) {
|
|
|
71
71
|
humanLog('');
|
|
72
72
|
humanLog(`No results for "${q}".`);
|
|
73
73
|
humanLog('');
|
|
74
|
-
humanLog(`Try a broader term, or browse: ${run} component --list`);
|
|
74
|
+
humanLog(`Try a broader term, or browse: ${run} astryx component --list`);
|
|
75
75
|
humanLog('');
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
@@ -86,7 +86,7 @@ export function registerSearch(program) {
|
|
|
86
86
|
if (r.description) {
|
|
87
87
|
humanLog(` ${r.description}`);
|
|
88
88
|
}
|
|
89
|
-
humanLog(` → ${
|
|
89
|
+
humanLog(` → ${run} ${r.command}`);
|
|
90
90
|
if (options.detail) {
|
|
91
91
|
if (r.import) humanLog(` import: ${r.import}`);
|
|
92
92
|
humanLog(` match: ${r.reason} (score ${r.score})`);
|