@astryxdesign/cli 0.4.1 → 0.4.2-canary.464a445
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 +34 -0
- package/README.md +1 -0
- package/api/init/init.doc.mjs +2 -2
- package/api/init/init.test.mjs +19 -2
- package/api/init/init.type.d.mts +9 -1
- package/api/init/init.type.mjs +3 -1
- package/api/init/run/run.mjs +36 -6
- package/api/theme/add/add.mjs +2 -13
- package/api/theme/build/build.font-warning.test.mjs +161 -0
- package/api/theme/build/build.mjs +19 -12
- package/api/theme/build/build.test.mjs +53 -0
- package/api/theme/build/font-warning.d.mts +26 -0
- package/api/theme/build/font-warning.mjs +214 -0
- package/api/theme/build/font-warning.test.mjs +242 -0
- package/api/theme/template/template.d.mts +21 -0
- package/api/theme/template/template.mjs +69 -0
- package/api/theme/template/template.test.mjs +82 -0
- package/api/theme/theme.d.mts +1 -0
- package/api/theme/theme.mjs +1 -0
- package/api/theme/theme.type.d.mts +13 -0
- package/api/theme/theme.type.mjs +11 -1
- package/api/theme/themeTemplate.doc.d.mts +11 -0
- package/api/theme/themeTemplate.doc.mjs +64 -0
- package/assets/docs/getting-started.doc.mjs +11 -1
- package/assets/docs/theme.doc.dense.mjs +2 -2
- package/assets/docs/theme.doc.mjs +20 -9
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/typography.doc.mjs +38 -0
- package/assets/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleCustomContent.doc.mjs +13 -0
- package/assets/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleCustomContent.tsx +55 -0
- package/assets/templates/pages/ai-chat/page.tsx +18 -13
- package/assets/templates/themes/butter/butterTheme.ts +4 -1
- package/assets/templates/themes/chocolate/chocolateTheme.ts +4 -1
- package/assets/templates/themes/gothic/gothicTheme.ts +4 -1
- package/assets/templates/themes/stone/stoneTheme.ts +4 -1
- package/assets/theme.template.ts +322 -0
- package/authoring/doctypes/base/type.ts +9 -1
- package/authoring/doctypes/component/component.doc.mjs +1 -1
- package/clients/cli/commands/build-theme.font-warning.test.mjs +138 -0
- package/clients/cli/commands/build-theme.mjs +50 -0
- package/clients/cli/commands/init.behavior.test.mjs +13 -2
- package/clients/cli/commands/theme-template.behavior.test.mjs +85 -0
- package/clients/cli/commands/theme-template.doc.mjs +42 -0
- package/clients/cli/commands/theme.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/response/response-types.doc.mjs +5 -0
- package/foundation/text/copyright-header.d.mts +11 -0
- package/foundation/text/copyright-header.mjs +35 -0
- package/foundation/text/copyright-header.test.mjs +58 -0
- package/package.json +9 -9
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CLI behavior for `astryx theme template`.
|
|
5
|
+
*
|
|
6
|
+
* The API leaf is covered by api/theme/template/template.test.mjs; what is only
|
|
7
|
+
* reachable here is the terminal binding — which message the user sees, and the
|
|
8
|
+
* JSON envelope. The first version of this command read `result.written`
|
|
9
|
+
* instead of `result.data.written`, so it wrote the file and then told the user
|
|
10
|
+
* it had skipped: a receipt read at the wrong depth is invisible to a unit test
|
|
11
|
+
* of the leaf.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import * as os from 'node:os';
|
|
18
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
19
|
+
|
|
20
|
+
let tmpDir;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-cli-theme-template-'));
|
|
23
|
+
fs.writeFileSync(
|
|
24
|
+
path.join(tmpDir, 'package.json'),
|
|
25
|
+
JSON.stringify({name: 'tmp', private: true}),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const read = f => fs.readFileSync(path.join(tmpDir, f), 'utf-8');
|
|
33
|
+
|
|
34
|
+
describe('astryx theme template', () => {
|
|
35
|
+
it('writes the template and says it wrote it', async () => {
|
|
36
|
+
const {status, stdout} = await runCli(['theme', 'template'], {cwd: tmpDir});
|
|
37
|
+
|
|
38
|
+
expect(status).toBe(0);
|
|
39
|
+
expect(stdout).toMatch(/Wrote theme\.template\.ts/);
|
|
40
|
+
expect(stdout).not.toMatch(/already exists/);
|
|
41
|
+
expect(read('theme.template.ts')).toMatch(/defineTheme/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('leaves an existing file alone, and says that instead', async () => {
|
|
45
|
+
fs.writeFileSync(path.join(tmpDir, 'theme.template.ts'), '// mine\n');
|
|
46
|
+
|
|
47
|
+
const {status, stdout} = await runCli(['theme', 'template'], {cwd: tmpDir});
|
|
48
|
+
|
|
49
|
+
expect(status).toBe(0);
|
|
50
|
+
expect(stdout).toMatch(/already exists/);
|
|
51
|
+
expect(read('theme.template.ts')).toBe('// mine\n');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('replaces it with --overwrite', async () => {
|
|
55
|
+
fs.writeFileSync(path.join(tmpDir, 'theme.template.ts'), '// mine\n');
|
|
56
|
+
|
|
57
|
+
const {status} = await runCli(['theme', 'template', '--overwrite'], {cwd: tmpDir});
|
|
58
|
+
|
|
59
|
+
expect(status).toBe(0);
|
|
60
|
+
expect(read('theme.template.ts')).toMatch(/defineTheme/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns a theme.template envelope under --json', async () => {
|
|
64
|
+
const {status, stdout} = await runCli(['--json', 'theme', 'template'], {cwd: tmpDir});
|
|
65
|
+
|
|
66
|
+
expect(status).toBe(0);
|
|
67
|
+
const payload = JSON.parse(stdout);
|
|
68
|
+
expect(payload.type).toBe('theme.template');
|
|
69
|
+
expect(payload.data).toEqual({
|
|
70
|
+
path: 'theme.template.ts',
|
|
71
|
+
written: true,
|
|
72
|
+
reason: null,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('refuses a path that escapes the project', async () => {
|
|
77
|
+
const {status, stderr} = await runCli(['theme', 'template', '../escaped.ts'], {
|
|
78
|
+
cwd: tmpDir,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(status).toBe(1);
|
|
82
|
+
expect(stderr).toMatch(/outside the project root/);
|
|
83
|
+
expect(fs.existsSync(path.join(path.dirname(tmpDir), 'escaped.ts'))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandDoc for `astryx theme template`. The terminal binding of the
|
|
5
|
+
* `themeTemplate()` function (referenced via `fn`); its args/flags map to that
|
|
6
|
+
* function's params so a converter can build Commander config + --help from one
|
|
7
|
+
* source of truth.
|
|
8
|
+
* @position packages/cli/clients/cli/commands — command documentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @type {import('@astryxdesign/cli/authoring').CommandDoc} */
|
|
12
|
+
export const doc = {
|
|
13
|
+
type: 'command',
|
|
14
|
+
name: 'theme template',
|
|
15
|
+
displayName: 'astryx theme template',
|
|
16
|
+
namespace: 'cli',
|
|
17
|
+
summary: 'Write the annotated theme template into your project',
|
|
18
|
+
description:
|
|
19
|
+
'Writes theme.template.ts: the annotated reference for the whole theme surface — every ' +
|
|
20
|
+
'defineTheme field, the token families, the component override syntax, and how a theme is ' +
|
|
21
|
+
'consumed — naming the CLI command that prints the authoritative reference for each. Read ' +
|
|
22
|
+
'it, copy what you need into your own theme file, delete it. Use `theme add <slug>` instead ' +
|
|
23
|
+
'to start from a theme we ship. Leaves an existing file untouched unless --overwrite.',
|
|
24
|
+
fn: 'themeTemplate',
|
|
25
|
+
args: [{name: 'path', param: 'options.targetPath', required: false}],
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
flag: '-f, --overwrite',
|
|
29
|
+
param: 'options.overwrite',
|
|
30
|
+
description: 'Replace an existing file',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
examples: [
|
|
34
|
+
{label: 'Write it at the project root', cli: 'astryx theme template'},
|
|
35
|
+
{label: 'Somewhere else', cli: 'astryx theme template src/themes/starter.ts'},
|
|
36
|
+
],
|
|
37
|
+
exitCodes: [
|
|
38
|
+
{code: 0, when: 'success, including when an existing file was left untouched'},
|
|
39
|
+
{code: 1, when: 'the target path escapes the project'},
|
|
40
|
+
],
|
|
41
|
+
related: ['theme add', 'theme list', 'theme build'],
|
|
42
|
+
};
|
|
@@ -17,8 +17,8 @@ export const doc = {
|
|
|
17
17
|
description:
|
|
18
18
|
'The theme command group. Running astryx theme with no subcommand prints the ' +
|
|
19
19
|
'subcommand list; the work happens in the subcommands: compile a theme (build), ' +
|
|
20
|
-
'scaffold one into your project (add), or list the bundled themes (list).',
|
|
21
|
-
subcommands: ['build', 'add', 'list'],
|
|
20
|
+
'scaffold one into your project (add), start a custom one from the annotated template (template), or list the bundled themes (list).',
|
|
21
|
+
subcommands: ['build', 'add', 'list', 'template'],
|
|
22
22
|
examples: [
|
|
23
23
|
{label: 'List bundled themes', cli: 'astryx theme list'},
|
|
24
24
|
{label: 'Scaffold a theme', cli: 'astryx theme add matcha'},
|
package/clients/cli/index.mjs
CHANGED
|
@@ -75,6 +75,7 @@ export const RESPONSE_TYPES = {
|
|
|
75
75
|
'theme build': ['theme.build', 'theme.build.check'],
|
|
76
76
|
'theme list': ['theme.list'],
|
|
77
77
|
'theme add': ['theme.list', 'theme.add'],
|
|
78
|
+
'theme template': ['theme.template'],
|
|
78
79
|
upgrade: ['upgrade.list', 'upgrade.status', 'upgrade.run'],
|
|
79
80
|
manifest: ['manifest'],
|
|
80
81
|
doctor: ['doctor'],
|
|
@@ -113,6 +114,7 @@ const EXAMPLES = {
|
|
|
113
114
|
'astryx theme add matcha',
|
|
114
115
|
'astryx theme add matcha ./src/themes/matcha',
|
|
115
116
|
],
|
|
117
|
+
'theme template': ['astryx theme template', 'astryx theme template --json'],
|
|
116
118
|
upgrade: ['astryx upgrade --json'],
|
|
117
119
|
manifest: ['astryx manifest --json', 'astryx --json'],
|
|
118
120
|
doctor: ['astryx doctor', 'astryx doctor --json'],
|
|
@@ -354,7 +354,7 @@ export function generateCompressedIndex(version, {coreDir, invocation = getCliIn
|
|
|
354
354
|
} else {
|
|
355
355
|
lines.push("- Custom styling: component props first; else style/className with tokens — var(--color-*|--spacing-*|--radius-*). No raw hex/px. (No StyleX/Tailwind compiler here — don't use xstyle/utility classes.)");
|
|
356
356
|
}
|
|
357
|
-
lines.push('- Tokens for every value (`astryx docs tokens`). Brand/accent
|
|
357
|
+
lines.push('- Tokens for every value (`astryx docs tokens`). Brand/accent belongs in the theme (`astryx theme list` / `theme add <slug>`, or `astryx theme template` for a custom one) — never override --color-* in :root.');
|
|
358
358
|
// Self-check — post-generation pass. Validated via vibe tests (internal/vibe-tests/
|
|
359
359
|
// prompt-purity-test): on complex multi-step UIs the rules above alone still leave raw
|
|
360
360
|
// CSS in ~11-13% of runs; a re-read-and-fix pass cuts that ~4x at negligible token cost.
|
|
@@ -183,6 +183,11 @@ export const doc = {
|
|
|
183
183
|
description:
|
|
184
184
|
'A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written.',
|
|
185
185
|
},
|
|
186
|
+
{
|
|
187
|
+
value: 'theme.template',
|
|
188
|
+
description:
|
|
189
|
+
'A write receipt for the annotated theme template: the path (relative to cwd), whether it was written, and the reason it was not — `exists` when a file was already there, which is a success.',
|
|
190
|
+
},
|
|
186
191
|
|
|
187
192
|
// upgrade
|
|
188
193
|
{
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Remove the leading Meta copyright header, preserving any BOM/shebang before
|
|
6
|
+
* it. Returns the source unchanged when the header is absent.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} source
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function stripCopyrightHeader(source: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Strips our repo's copyright header from files we scaffold into someone
|
|
5
|
+
* else's project.
|
|
6
|
+
*
|
|
7
|
+
* Every file in this repo carries the Meta copyright header, and several
|
|
8
|
+
* commands copy repo files out verbatim — `theme add` (bundled theme sources),
|
|
9
|
+
* `init --features theme` (the annotated theme template). A consumer's own
|
|
10
|
+
* source tree should not inherit our boilerplate, and their lint may well
|
|
11
|
+
* reject it.
|
|
12
|
+
*
|
|
13
|
+
* SYNC: the same regex is applied by apps/docsite/src/lib/codeExamples.ts for
|
|
14
|
+
* rendered code samples. That copy lives in a different package (the docsite
|
|
15
|
+
* cannot import CLI internals), so a change to the header format has to land in
|
|
16
|
+
* both.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Matches our header at the very start of a file, with the leading BOM and/or
|
|
21
|
+
* shebang captured so they survive the strip.
|
|
22
|
+
*/
|
|
23
|
+
const META_COPYRIGHT_HEADER_RE =
|
|
24
|
+
/^(\uFEFF?(?:#![^\r\n]*(?:\r?\n))?)\/\/ Copyright \(c\) Meta Platforms, Inc\. and affiliates\.\r?\n(?:\r?\n)*/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Remove the leading Meta copyright header, preserving any BOM/shebang before
|
|
28
|
+
* it. Returns the source unchanged when the header is absent.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} source
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function stripCopyrightHeader(source) {
|
|
34
|
+
return source.replace(META_COPYRIGHT_HEADER_RE, '$1');
|
|
35
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Unit tests for the shared copyright-header stripper.
|
|
5
|
+
*
|
|
6
|
+
* Two commands copy repo files into a consumer's project (`theme add`,
|
|
7
|
+
* `init --features theme`); this is what keeps our boilerplate out of their
|
|
8
|
+
* tree. The BOM/shebang cases are the ones worth pinning — a naive strip
|
|
9
|
+
* corrupts the file rather than merely leaving a stray comment.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {stripCopyrightHeader} from './copyright-header.mjs';
|
|
14
|
+
|
|
15
|
+
const HEADER = '// Copyright (c) Meta Platforms, Inc. and affiliates.\n';
|
|
16
|
+
|
|
17
|
+
describe('stripCopyrightHeader', () => {
|
|
18
|
+
it('removes the header and the blank line after it', () => {
|
|
19
|
+
expect(stripCopyrightHeader(`${HEADER}\nexport const a = 1;\n`)).toBe(
|
|
20
|
+
'export const a = 1;\n',
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('removes it with no blank line after', () => {
|
|
25
|
+
expect(stripCopyrightHeader(`${HEADER}export const a = 1;\n`)).toBe(
|
|
26
|
+
'export const a = 1;\n',
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('keeps a shebang, which must stay on line 1', () => {
|
|
31
|
+
expect(stripCopyrightHeader(`#!/usr/bin/env node\n${HEADER}\nrun();\n`)).toBe(
|
|
32
|
+
'#!/usr/bin/env node\nrun();\n',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps a BOM', () => {
|
|
37
|
+
expect(stripCopyrightHeader(`\uFEFF${HEADER}\nexport const a = 1;\n`)).toBe(
|
|
38
|
+
'\uFEFFexport const a = 1;\n',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('handles CRLF line endings', () => {
|
|
43
|
+
expect(
|
|
44
|
+
stripCopyrightHeader(
|
|
45
|
+
'// Copyright (c) Meta Platforms, Inc. and affiliates.\r\n\r\nexport const a = 1;\r\n',
|
|
46
|
+
),
|
|
47
|
+
).toBe('export const a = 1;\r\n');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('leaves a file without the header alone', () => {
|
|
51
|
+
expect(stripCopyrightHeader('export const a = 1;\n')).toBe('export const a = 1;\n');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('only strips at the top — a mention further down is content', () => {
|
|
55
|
+
const source = `export const a = 1;\n${HEADER}`;
|
|
56
|
+
expect(stripCopyrightHeader(source)).toBe(source);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2-canary.464a445",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"zod": "^4.4.3"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@astryxdesign/charts": "
|
|
88
|
-
"@astryxdesign/core": "
|
|
89
|
-
"@astryxdesign/lab": "
|
|
90
|
-
"@astryxdesign/theme-neutral": "
|
|
87
|
+
"@astryxdesign/charts": "0.4.2-canary.464a445",
|
|
88
|
+
"@astryxdesign/core": "0.4.2-canary.464a445",
|
|
89
|
+
"@astryxdesign/lab": "0.4.2-canary.464a445",
|
|
90
|
+
"@astryxdesign/theme-neutral": "0.4.2-canary.464a445",
|
|
91
91
|
"gpt-tokenizer": "^3.4.0"
|
|
92
92
|
},
|
|
93
93
|
"peerDependenciesMeta": {
|
|
@@ -105,10 +105,10 @@
|
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@astryxdesign/charts": "
|
|
109
|
-
"@astryxdesign/core": "
|
|
110
|
-
"@astryxdesign/lab": "
|
|
111
|
-
"@astryxdesign/theme-neutral": "
|
|
108
|
+
"@astryxdesign/charts": "0.4.2-canary.464a445",
|
|
109
|
+
"@astryxdesign/core": "0.4.2-canary.464a445",
|
|
110
|
+
"@astryxdesign/lab": "0.4.2-canary.464a445",
|
|
111
|
+
"@astryxdesign/theme-neutral": "0.4.2-canary.464a445",
|
|
112
112
|
"gpt-tokenizer": "^3.4.0"
|
|
113
113
|
},
|
|
114
114
|
"scripts": {
|