@astryxdesign/cli 0.1.6-canary.ff5dfca → 0.1.7-canary.04cd8f7
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/CHANGELOG.md +33 -0
- package/README.md +115 -19
- package/docs/cli-integrations.doc.mjs +150 -0
- package/docs/getting-started.doc.mjs +9 -9
- 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 +9 -10
- package/src/api/doctor.mjs +3 -3
- package/src/codemods/ensure-jscodeshift.mjs +11 -27
- package/src/codemods/run-codemod.mjs +1 -1
- package/src/codemods/runner.mjs +2 -2
- package/src/commands/agent-docs.mjs +7 -8
- package/src/commands/agent-docs.test.mjs +11 -4
- package/src/commands/build-theme.mjs +10 -71
- package/src/commands/build.mjs +15 -15
- package/src/commands/component/index.mjs +4 -4
- package/src/commands/discover.mjs +7 -5
- package/src/commands/docs.mjs +4 -4
- package/src/commands/hook/index.mjs +4 -4
- package/src/commands/init.mjs +48 -152
- package/src/commands/init.next-steps.test.mjs +1 -1
- package/src/commands/interactive-guard.test.mjs +19 -22
- 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 +11 -34
- package/src/commands/template.mjs +11 -31
- package/src/commands/upgrade.mjs +9 -6
- package/src/commands/upgrade.test.mjs +1 -1
- package/src/index.mjs +5 -6
- package/src/lib/component-format.mjs +2 -1
- package/src/lib/term-log.mjs +48 -0
- package/src/utils/package-manager.mjs +78 -0
- package/src/utils/package-manager.test.mjs +108 -1
- package/src/utils/path-safety.mjs +0 -18
- package/src/utils/update-check.mjs +2 -1
- package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
- package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
- package/docs/integration-authoring.md +0 -105
- package/src/utils/interactive.mjs +0 -76
- package/src/utils/interactive.test.mjs +0 -70
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Authoring an Astryx Integration
|
|
2
|
-
|
|
3
|
-
> **Status:** working notes. This should eventually move to the public wiki
|
|
4
|
-
> alongside the rest of the integration-authoring guidance; it lives here for
|
|
5
|
-
> now so it ships and is versioned with the CLI.
|
|
6
|
-
|
|
7
|
-
An **Integration** is an npm package that contributes components, templates, and/or
|
|
8
|
-
codemods to a consumer's design-system workflow. Consumers install the 3rd party
|
|
9
|
-
package, add a line to their astryx.config file:
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import {createConfig} from '@astryxdesign/cli/config';
|
|
13
|
-
|
|
14
|
-
export default createConfig({
|
|
15
|
-
integrations: ['@acme/astryx-widgets'],
|
|
16
|
-
...
|
|
17
|
-
});
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Then the integration's components and templates will be surfaced alongside Astryx
|
|
21
|
-
components in the Astryx CLI.
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
astryx component AcmeCarousel --props
|
|
25
|
-
astryx component --list --package @acme/astryx-widgets
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## The Integration File
|
|
29
|
-
|
|
30
|
-
In order to register your package as an Astryx Integration, create an
|
|
31
|
-
`astryx.integration.{ts,mjs,js}` file as a sibling to your `package.json`. This file
|
|
32
|
-
tells Astryx where to find your components, templates, codemods, etc.
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
// astryx.integration.{ts,mjs,js}
|
|
36
|
-
import {createIntegration} from '@astryxdesign/cli/integration';
|
|
37
|
-
|
|
38
|
-
export default createIntegration({
|
|
39
|
-
components: './components',
|
|
40
|
-
templates: './templates',
|
|
41
|
-
codemods: './codemods',
|
|
42
|
-
issuesUrl: 'https://github.com/acme/widgets/issues',
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Components
|
|
47
|
-
|
|
48
|
-
Your components themselves may be exported from your library as you see fit (consumers
|
|
49
|
-
will still import them from your package) but Astryx CLI will look for a .doc.{ts,mjs,js}
|
|
50
|
-
file with the same stem e.g. `AcmeCarousel.tsx` and `AcmeCarousel.doc.ts`.
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
// AcmeCarousel.doc.ts
|
|
54
|
-
import {createComponentDoc} from '@astryxdesign/cli/doc';
|
|
55
|
-
|
|
56
|
-
export default createComponentDoc({
|
|
57
|
-
name: 'AcmeCarousel',
|
|
58
|
-
description: '...',
|
|
59
|
-
...
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Templates
|
|
64
|
-
|
|
65
|
-
Templates are typically not exported from the package directly, but instead accessed
|
|
66
|
-
via the Astryx CLI. Consumers can look through your templates and materialize them
|
|
67
|
-
into their apps.
|
|
68
|
-
|
|
69
|
-
You define a template with the `createPageTemplate` (for full pages) or `createBlockTemplate`
|
|
70
|
-
(for smaller chunks). e.g. `AcmeLandingPage.tsx`, `AcmeLandingPage.template.ts`
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
// AcmeLandingPage.template.ts
|
|
74
|
-
import {createPageTemplate} from '@astryxdesign/cli/template';
|
|
75
|
-
|
|
76
|
-
export default createPageTemplate({
|
|
77
|
-
...
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Note that, since the CLI needs access to the template source code, you need to make sure
|
|
82
|
-
that it is included in your published package. This will also allow us to render previews
|
|
83
|
-
of templates in the future by bundling your template into a doc site build.
|
|
84
|
-
|
|
85
|
-
Typically, this is done via the package.json `exports` key.
|
|
86
|
-
|
|
87
|
-
```jsonc
|
|
88
|
-
{
|
|
89
|
-
"exports": {
|
|
90
|
-
// ...
|
|
91
|
-
"./templates/*.tsx": "./templates/*.tsx",
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
In order to verify that it's working, you can test importing the template component like this:
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
import('@acme/astryx-widgets/templates/AcmeLandingPage.tsx');
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Import **with the `.tsx` extension** — an extensionless specifier won't resolve
|
|
103
|
-
under `moduleResolution: bundler`. The extensionful `"./templates/*.tsx"` export
|
|
104
|
-
above is what lets that import type-check without consumers enabling
|
|
105
|
-
`allowImportingTsExtensions`.
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Interactivity contract for the CLI.
|
|
5
|
-
*
|
|
6
|
-
* A single source of truth for "can this process prompt the user?". Several
|
|
7
|
-
* commands launch @clack/prompts wizards; in a non-interactive context (CI,
|
|
8
|
-
* piped stdin/stdout, no TTY) those prompts block forever. Historically each
|
|
9
|
-
* command answered this question differently — some checked `stdout.isTTY`,
|
|
10
|
-
* one checked `stdin.isTTY`, one added `!process.env.CI`, and some had no
|
|
11
|
-
* guard at all. This module centralizes the check so every command behaves
|
|
12
|
-
* identically.
|
|
13
|
-
*
|
|
14
|
-
* Two entry points, for two situations:
|
|
15
|
-
*
|
|
16
|
-
* - `requireInteractive()` — for commands whose prompt IS the work
|
|
17
|
-
* (e.g. `astryx init`, `astryx theme`). With no TTY there is nothing to do, so
|
|
18
|
-
* fail fast (exit 1) with actionable, non-interactive guidance.
|
|
19
|
-
*
|
|
20
|
-
* - `isInteractive()` — for commands with an OPTIONAL secondary prompt
|
|
21
|
-
* that runs after the primary work has already succeeded; callers use
|
|
22
|
-
* this to skip the prompt gracefully in non-interactive contexts.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* True when the process can safely run an interactive prompt.
|
|
27
|
-
*
|
|
28
|
-
* Requires BOTH stdin and stdout to be TTYs (clack reads stdin and renders to
|
|
29
|
-
* stdout) and that we are not in a CI environment. A CI runner may allocate a
|
|
30
|
-
* pseudo-TTY, so `process.env.CI` is an explicit override: never prompt in CI.
|
|
31
|
-
*
|
|
32
|
-
* @param {object} [env] - Override hook for tests.
|
|
33
|
-
* @param {boolean} [env.stdinTTY=process.stdin.isTTY]
|
|
34
|
-
* @param {boolean} [env.stdoutTTY=process.stdout.isTTY]
|
|
35
|
-
* @param {boolean} [env.ci=Boolean(process.env.CI)]
|
|
36
|
-
* @returns {boolean}
|
|
37
|
-
*/
|
|
38
|
-
export function isInteractive({
|
|
39
|
-
stdinTTY = Boolean(process.stdin && process.stdin.isTTY),
|
|
40
|
-
stdoutTTY = Boolean(process.stdout && process.stdout.isTTY),
|
|
41
|
-
ci = Boolean(process.env.CI),
|
|
42
|
-
} = {}) {
|
|
43
|
-
if (ci) return false;
|
|
44
|
-
return stdinTTY && stdoutTTY;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Guard for commands whose primary action is an interactive wizard. When the
|
|
49
|
-
* process is non-interactive, prints an actionable error and exits 1 instead
|
|
50
|
-
* of hanging on a prompt that will never receive input.
|
|
51
|
-
*
|
|
52
|
-
* @param {object} options
|
|
53
|
-
* @param {string} options.command - Command name for the message, e.g. 'init'.
|
|
54
|
-
* @param {string} options.hint - Concrete non-interactive invocation, e.g.
|
|
55
|
-
* '`pnpm astryx init --all` or `--features agents,theme,template`'.
|
|
56
|
-
* @param {boolean} [options.json=false] - When true, the command does not
|
|
57
|
-
* support --json; we still exit 1 but skip the human-formatted guidance.
|
|
58
|
-
* @param {object} [env] - Forwarded to isInteractive (test hook).
|
|
59
|
-
* @returns {void} Returns when interactive; otherwise calls process.exit(1).
|
|
60
|
-
*/
|
|
61
|
-
export function requireInteractive({command, hint, json = false} = {}, env) {
|
|
62
|
-
if (isInteractive(env)) return;
|
|
63
|
-
const name = command ? `astryx ${command}` : 'this command';
|
|
64
|
-
console.error(
|
|
65
|
-
`Error: \`${name}\` with no flags is interactive and requires a TTY.`,
|
|
66
|
-
);
|
|
67
|
-
if (hint) {
|
|
68
|
-
console.error(`Run non-interactively with: ${hint}`);
|
|
69
|
-
}
|
|
70
|
-
if (!json) {
|
|
71
|
-
console.error(
|
|
72
|
-
'Detected a non-interactive environment (no TTY, piped I/O, or CI=1).',
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Unit tests for the interactivity contract.
|
|
5
|
-
*
|
|
6
|
-
* isInteractive() is pure given its injected env, so these are fast unit
|
|
7
|
-
* tests. The "does the command actually fail fast instead of hanging" proof
|
|
8
|
-
* lives in the per-command subprocess tests (init/theme non-interactive).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {describe, it, expect, vi, afterEach} from 'vitest';
|
|
12
|
-
import {isInteractive, requireInteractive} from './interactive.mjs';
|
|
13
|
-
|
|
14
|
-
describe('isInteractive', () => {
|
|
15
|
-
it('is true only when stdin AND stdout are TTYs and not CI', () => {
|
|
16
|
-
expect(isInteractive({stdinTTY: true, stdoutTTY: true, ci: false})).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('is false when stdin is not a TTY (piped input)', () => {
|
|
20
|
-
expect(isInteractive({stdinTTY: false, stdoutTTY: true, ci: false})).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('is false when stdout is not a TTY (piped output)', () => {
|
|
24
|
-
expect(isInteractive({stdinTTY: true, stdoutTTY: false, ci: false})).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('is false in CI even with a pseudo-TTY on both streams', () => {
|
|
28
|
-
expect(isInteractive({stdinTTY: true, stdoutTTY: true, ci: true})).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('requireInteractive', () => {
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
vi.restoreAllMocks();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('returns (does not exit) when interactive', () => {
|
|
38
|
-
const exit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
39
|
-
throw new Error('exit should not be called');
|
|
40
|
-
});
|
|
41
|
-
expect(() =>
|
|
42
|
-
requireInteractive(
|
|
43
|
-
{command: 'init', hint: '`astryx init --all`'},
|
|
44
|
-
{stdinTTY: true, stdoutTTY: true, ci: false},
|
|
45
|
-
),
|
|
46
|
-
).not.toThrow();
|
|
47
|
-
expect(exit).not.toHaveBeenCalled();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('exits 1 with actionable guidance when non-interactive', () => {
|
|
51
|
-
const exit = vi
|
|
52
|
-
.spyOn(process, 'exit')
|
|
53
|
-
.mockImplementation(() => {
|
|
54
|
-
throw new Error('__exit__');
|
|
55
|
-
});
|
|
56
|
-
const err = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
57
|
-
expect(() =>
|
|
58
|
-
requireInteractive(
|
|
59
|
-
{command: 'theme', hint: '`astryx theme <preset>`'},
|
|
60
|
-
{stdinTTY: false, stdoutTTY: false, ci: false},
|
|
61
|
-
),
|
|
62
|
-
).toThrow('__exit__');
|
|
63
|
-
expect(exit).toHaveBeenCalledWith(1);
|
|
64
|
-
const output = err.mock.calls.map(c => c.join(' ')).join('\n');
|
|
65
|
-
expect(output).toMatch(/requires a TTY/i);
|
|
66
|
-
expect(output).toMatch(/astryx theme <preset>/);
|
|
67
|
-
expect(output).toMatch(/`astryx theme`/);
|
|
68
|
-
expect(output).not.toMatch(/\bxds\b/);
|
|
69
|
-
});
|
|
70
|
-
});
|