@astryxdesign/cli 0.7.12 → 0.7.13-canary.1719285
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/api/doctor/_adapter.d.mts +17 -0
- package/api/doctor/_adapter.mjs +40 -0
- package/api/doctor/doctor.d.mts +34 -4
- package/api/doctor/doctor.mjs +108 -21
- package/api/icon/_adapter.d.mts +6 -3
- package/api/icon/_adapter.mjs +11 -3
- package/api/icon/icon.type.d.mts +22 -1
- package/api/icon/icon.type.mjs +12 -4
- package/api/icon/iconUse.doc.mjs +27 -7
- package/api/icon/list/list.mjs +29 -3
- package/api/icon/use/use.d.mts +7 -3
- package/api/icon/use/use.mjs +68 -18
- package/api/integration/validate-integration.d.mts +4 -0
- package/api/integration/validate-integration.mjs +32 -2
- package/api/integration/validate-integration.type.d.mts +15 -0
- package/api/integration/validate-integration.type.mjs +6 -1
- package/api/template/copy/copy.mjs +4 -2
- package/api/template/template.type.d.mts +42 -4
- package/api/template/template.type.mjs +9 -6
- package/assets/discover/official-packages.json +4 -0
- package/assets/docs/tree/icon-sets.doc.mjs +91 -7
- package/assets/icons/definitions/alignBottom.doc.mjs +9 -0
- package/assets/icons/definitions/alignJustify.doc.mjs +9 -0
- package/assets/icons/definitions/alignMiddle.doc.mjs +9 -0
- package/assets/icons/definitions/alignTop.doc.mjs +9 -0
- package/assets/icons/definitions/angle.doc.mjs +9 -0
- package/assets/icons/definitions/cloud.doc.mjs +9 -0
- package/assets/icons/definitions/collection.doc.mjs +9 -0
- package/assets/icons/definitions/contrast.doc.mjs +9 -0
- package/assets/icons/definitions/cornerRadius.doc.mjs +9 -0
- package/assets/icons/definitions/cut.doc.mjs +9 -0
- package/assets/icons/definitions/fingerprint.doc.mjs +9 -0
- package/assets/icons/definitions/fire.doc.mjs +9 -0
- package/assets/icons/definitions/frame.doc.mjs +9 -0
- package/assets/icons/definitions/gift.doc.mjs +9 -0
- package/assets/icons/definitions/group.doc.mjs +9 -0
- package/assets/icons/definitions/integration.doc.mjs +9 -0
- package/assets/icons/definitions/lowercase.doc.mjs +9 -0
- package/assets/icons/definitions/paddingHorizontal.doc.mjs +9 -0
- package/assets/icons/definitions/paddingPerSide.doc.mjs +9 -0
- package/assets/icons/definitions/paddingVertical.doc.mjs +9 -0
- package/assets/icons/definitions/panelBottom.doc.mjs +9 -0
- package/assets/icons/definitions/photoEffect.doc.mjs +9 -0
- package/assets/icons/definitions/radiusPerCorner.doc.mjs +9 -0
- package/assets/icons/definitions/rotateCounterclockwise.doc.mjs +9 -0
- package/assets/icons/definitions/tablet.doc.mjs +9 -0
- package/assets/icons/definitions/text.doc.mjs +9 -0
- package/assets/icons/definitions/textRegular.doc.mjs +9 -0
- package/assets/icons/definitions/transfer.doc.mjs +9 -0
- package/assets/icons/definitions/uppercase.doc.mjs +9 -0
- package/assets/icons/definitions/userRemove.doc.mjs +9 -0
- package/assets/templates/blocks/components/BaseTypeahead/BaseTypeaheadMenu.doc.mjs +23 -0
- package/assets/templates/blocks/components/BaseTypeahead/BaseTypeaheadMenu.tsx +199 -0
- package/assets/templates/blocks/components/ButtonGroup/ButtonGroupShowcase.doc.mjs +1 -0
- package/assets/templates/blocks/components/ChatSystemMessage/ChatSystemMessageStatusUpdates.doc.mjs +7 -2
- package/assets/templates/pages/canvas-editor/template.doc.mjs +20 -0
- package/assets/templates/pages/editor/template.doc.mjs +1 -0
- package/assets/templates/pages/form-wizard-vertical/template.doc.mjs +1 -0
- package/assets/templates/pages/settings-dialog/template.doc.mjs +4 -0
- package/assets/templates/pages/shell-top-nav/template.doc.mjs +3 -0
- package/assets/templates/pages/table-collapsible/template.doc.mjs +1 -0
- package/assets/templates/pages/table-comparison/template.doc.mjs +1 -0
- package/authoring/config/config.doc.mjs +4 -4
- package/authoring/config/parse.d.mts +1 -1
- package/authoring/config/parse.mjs +3 -1
- package/authoring/config/type.ts +7 -5
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +5 -3
- package/clients/cli/commands/experimental/icon.mjs +38 -9
- package/clients/cli/commands/experimental-icon-use.doc.mjs +9 -4
- package/clients/cli/commands/template.mjs +12 -3
- package/foundation/config/config-writer.d.mts +14 -0
- package/foundation/config/config-writer.mjs +73 -17
- package/foundation/discovery/icon-set-discovery.d.mts +77 -7
- package/foundation/discovery/icon-set-discovery.mjs +128 -42
- package/foundation/discovery/template-icon-notes.d.mts +46 -0
- package/foundation/discovery/template-icon-notes.mjs +94 -16
- package/foundation/discovery/template-icons.d.mts +247 -0
- package/foundation/discovery/template-icons.mjs +591 -37
- package/foundation/integrations/cli-requirement.d.mts +13 -4
- package/foundation/integrations/cli-requirement.mjs +18 -6
- package/package.json +6 -6
|
@@ -68,7 +68,7 @@ export const doc = {
|
|
|
68
68
|
description:
|
|
69
69
|
'Tell `{@command discover}` which integrations this project could add: an async function that returns a catalog. An integration can provide one too, as a `discover` named export from its manifest. Discover calls every source, yours first, and one that fails never hides the others. Discover only reads; your package manager installs.',
|
|
70
70
|
example:
|
|
71
|
-
|
|
71
|
+
'async ({signal, package: name, version}) => fetchCatalog({signal, name, version})',
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
name: 'experimental',
|
|
@@ -77,11 +77,11 @@ export const doc = {
|
|
|
77
77
|
fields: [
|
|
78
78
|
{
|
|
79
79
|
name: 'experimental.iconSet',
|
|
80
|
-
type: 'string',
|
|
80
|
+
type: 'string | string[]',
|
|
81
81
|
stability: 'experimental',
|
|
82
82
|
description:
|
|
83
|
-
"Experimental: may change or be removed in a patch release. The project's icon set, by set name or as `<package>/<set
|
|
84
|
-
example: "'@acme/outline-icons/outline'",
|
|
83
|
+
"Experimental: may change or be removed in a patch release. The project's main icon set, by set name or as `<package>/<set>`; or a list of them, the main set first and then backup sets in order. A copied template takes each icon from the first set in the list that has its meaning, and draws a placeholder the copied file declares when none has it. It is the opt-in: copied templates use these icons, and `astryx discover` and `astryx doctor` report icon sets, only when it is set. `astryx experimental icon use <main> --backup <set>` writes it. `astryx experimental icon` ranks the main set's icons first; without it, the only installed set is the project's there. `astryx search` never returns icons.",
|
|
84
|
+
example: "['@acme/outline-icons/outline', '@acme/brand-icons/brand']",
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
name: 'experimental.xle.components',
|
|
@@ -58,7 +58,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
58
58
|
default: z.ZodOptional<z.ZodBoolean>;
|
|
59
59
|
}, z.core.$strict>>>;
|
|
60
60
|
}, z.core.$strict>>;
|
|
61
|
-
iconSet: z.ZodOptional<z.ZodString
|
|
61
|
+
iconSet: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
62
62
|
}, z.core.$strict>>;
|
|
63
63
|
}, z.core.$strict>;
|
|
64
64
|
export {};
|
package/authoring/config/type.ts
CHANGED
|
@@ -123,12 +123,14 @@ export interface AstryxConfig {
|
|
|
123
123
|
components?: Record<string, XleComponent>;
|
|
124
124
|
};
|
|
125
125
|
/**
|
|
126
|
-
* Experimental icon sets (spec:AST-084): the project's icon set, a
|
|
127
|
-
* name (`"outline"`) or `"<package>/<set>"`, and the opt-in.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
126
|
+
* Experimental icon sets (spec:AST-084): the project's main icon set, a
|
|
127
|
+
* set name (`"outline"`) or `"<package>/<set>"`, and the opt-in. A list
|
|
128
|
+
* (`["outline", "@acme/icons/brand"]`) names the main set first, then
|
|
129
|
+
* backup sets in order: a copied template takes each icon from the first
|
|
130
|
+
* set that has its meaning. Copied templates use these icons, and
|
|
131
|
+
* discover and Doctor report icon sets, only when it is set.
|
|
130
132
|
* @experimental May change or be removed in a patch release.
|
|
131
133
|
*/
|
|
132
|
-
iconSet?: string;
|
|
134
|
+
iconSet?: string | string[];
|
|
133
135
|
};
|
|
134
136
|
}
|
|
@@ -69,7 +69,7 @@ export const doc = {
|
|
|
69
69
|
type: 'Record<string, string>',
|
|
70
70
|
stability: 'experimental',
|
|
71
71
|
description:
|
|
72
|
-
"The icon definition each icon the source imports draws, such as `{TrashIcon: 'delete'}`: a named import is keyed by the name it imports (`{Trash as TrashIcon}` is `Trash`), a default import by its local name (`import DeleteIcon from …` is `DeleteIcon`). When the project sets `experimental.iconSet`, `astryx template` (shown or copied) replaces each declared icon with the
|
|
72
|
+
"The icon definition each icon the source imports draws, such as `{TrashIcon: 'delete'}`: a named import is keyed by the name it imports (`{Trash as TrashIcon}` is `Trash`), a default import by its local name (`import DeleteIcon from …` is `DeleteIcon`). When the project sets `experimental.iconSet`, `astryx template` (shown or copied) replaces each declared icon with the icon for that definition (`astryx docs cli/experimental/icon-definitions`) from the first set in the project's list that maps it, or with a placeholder the copied file declares when no set does, and reports every key as mapped, a placeholder, or kept; an undeclared import is left alone, and `astryx integration check` notes it (`template_icon_undeclared`, failing `--strict --next`) in a package whose templates declare `icons`. A name imported from two modules (`TrashIcon` from an outline and a solid module) needs one key, and each import keeps its variant when its module belongs to an installed set.",
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
name: 'isReady',
|
|
@@ -55,9 +55,11 @@ export interface BaseTemplateDoc extends AuthoredDocGraphFields {
|
|
|
55
55
|
* (`{TrashIcon: 'delete'}`), keyed by the name a named import reads
|
|
56
56
|
* (`{Trash as TrashIcon}` is `Trash`) or a default import's local name
|
|
57
57
|
* (`import DeleteIcon from …` is `DeleteIcon`). When the project has an
|
|
58
|
-
* icon set, each declared icon is replaced by the
|
|
59
|
-
* definition (`astryx docs cli/experimental/icon-definitions`)
|
|
60
|
-
*
|
|
58
|
+
* icon set, each declared icon is replaced by the icon for that
|
|
59
|
+
* definition (`astryx docs cli/experimental/icon-definitions`) from the
|
|
60
|
+
* first set in the project's list that maps it, or by a placeholder the
|
|
61
|
+
* copied file declares when no set does, and every key is reported as mapped, a
|
|
62
|
+
* placeholder, or kept. A name imported from two modules
|
|
61
63
|
* (`TrashIcon` from an outline and a solid module) needs one key.
|
|
62
64
|
* @experimental May change or be removed in a patch release. */
|
|
63
65
|
icons?: Record<string, string>;
|
|
@@ -256,10 +256,17 @@ export function registerExperimentalIcon(group, program) {
|
|
|
256
256
|
return answered;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
const {status, projectSet, reason, sets, withdrawn} =
|
|
259
|
+
const {status, projectSet, reason, sets, withdrawn, order, next} =
|
|
260
|
+
result.data;
|
|
260
261
|
emit(
|
|
261
262
|
section('Icon sets'),
|
|
262
|
-
record({
|
|
263
|
+
record({
|
|
264
|
+
status,
|
|
265
|
+
projectSet,
|
|
266
|
+
...(order.length > 1 ? {order: order.join(' > ')} : {}),
|
|
267
|
+
reason,
|
|
268
|
+
}),
|
|
269
|
+
...(next ? [text(`[warn] ${next}`)] : []),
|
|
263
270
|
...(sets.length > 0
|
|
264
271
|
? [
|
|
265
272
|
records(
|
|
@@ -283,6 +290,7 @@ export function registerExperimentalIcon(group, program) {
|
|
|
283
290
|
'definitions',
|
|
284
291
|
'own',
|
|
285
292
|
'missing',
|
|
293
|
+
'role',
|
|
286
294
|
'project',
|
|
287
295
|
'description',
|
|
288
296
|
],
|
|
@@ -346,21 +354,42 @@ export function registerExperimentalIcon(group, program) {
|
|
|
346
354
|
|
|
347
355
|
defineCommand(iconCli, iconUseCommand, {
|
|
348
356
|
fn: iconUseFn,
|
|
349
|
-
action: (
|
|
357
|
+
action: (
|
|
358
|
+
/** @type {string} */ set,
|
|
359
|
+
/** @type {{backup?: string[]}} */ options,
|
|
360
|
+
) =>
|
|
350
361
|
run(
|
|
351
|
-
() => iconUse(set, {cwd: process.cwd()}),
|
|
352
|
-
({data}) =>
|
|
362
|
+
() => iconUse(set, {cwd: process.cwd(), backups: options.backup ?? []}),
|
|
363
|
+
({data}) => {
|
|
364
|
+
/** @type {Array<{iconSet: string, set: string}>} */
|
|
365
|
+
const backups = data.backups;
|
|
366
|
+
const shown = (/** @type {string | string[]} */ value) =>
|
|
367
|
+
Array.isArray(value) ? JSON.stringify(value) : `"${value}"`;
|
|
368
|
+
const main = `"${data.iconSet}" (${data.set})`;
|
|
369
|
+
const order =
|
|
370
|
+
backups.length > 0
|
|
371
|
+
? `main set ${main}, then ${backups.map(each => `"${each.iconSet}" (${each.set})`).join(', then ')}`
|
|
372
|
+
: main;
|
|
353
373
|
emit(
|
|
354
374
|
text(
|
|
355
375
|
data.changed
|
|
356
|
-
? `[ok] experimental.iconSet is now
|
|
357
|
-
: `[ok] experimental.iconSet already names
|
|
376
|
+
? `[ok] experimental.iconSet is now ${order} in ${data.config}${data.created ? ', created' : ''}.`
|
|
377
|
+
: `[ok] experimental.iconSet already names ${order} in ${data.config}.`,
|
|
358
378
|
),
|
|
379
|
+
...(backups.length > 0
|
|
380
|
+
? [
|
|
381
|
+
text(
|
|
382
|
+
'A copied template takes each icon from the first set in this order that has its meaning.',
|
|
383
|
+
),
|
|
384
|
+
]
|
|
385
|
+
: []),
|
|
359
386
|
...(data.previous && data.changed
|
|
360
|
-
? [text(`It was
|
|
387
|
+
? [text(`It was ${shown(data.previous)}.`)]
|
|
361
388
|
: []),
|
|
389
|
+
...(data.next ? [text(`[warn] ${data.next}`)] : []),
|
|
362
390
|
...(data.warning ? [text(`[warn] ${data.warning}`)] : []),
|
|
363
|
-
)
|
|
391
|
+
);
|
|
392
|
+
},
|
|
364
393
|
),
|
|
365
394
|
});
|
|
366
395
|
|
|
@@ -9,13 +9,18 @@ export const doc = {
|
|
|
9
9
|
displayName: 'astryx experimental icon use',
|
|
10
10
|
namespace: 'cli/commands',
|
|
11
11
|
stability: 'experimental',
|
|
12
|
-
summary: "Choose the project's icon set",
|
|
12
|
+
summary: "Choose the project's main icon set and backups",
|
|
13
13
|
description:
|
|
14
|
-
"Writes `experimental.iconSet` in astryx.config, so copied templates and blocks use that set's icons.
|
|
14
|
+
"Writes `experimental.iconSet` in astryx.config, so copied templates and blocks use that set's icons. With `--backup`, it writes a list: the main set first, then each backup in the order given. A copied template takes each icon from the first set in the list that has its meaning. Every set must be installed. Experimental: it may change or be removed in a patch release.",
|
|
15
15
|
fn: 'iconUse',
|
|
16
16
|
args: [{name: 'set', param: 'set', required: true}],
|
|
17
|
+
options: [{flag: '--backup <set...>', param: 'options.backups'}],
|
|
17
18
|
examples: [
|
|
18
19
|
{label: 'Use a set', cli: 'astryx experimental icon use outline'},
|
|
20
|
+
{
|
|
21
|
+
label: 'Use a main set with a backup',
|
|
22
|
+
cli: 'astryx experimental icon use outline --backup @acme/brand-icons/brand',
|
|
23
|
+
},
|
|
19
24
|
{
|
|
20
25
|
label: 'Name its package',
|
|
21
26
|
cli: 'astryx experimental icon use @acme/icons/outline',
|
|
@@ -25,11 +30,11 @@ export const doc = {
|
|
|
25
30
|
exitCodes: [
|
|
26
31
|
{
|
|
27
32
|
code: 0,
|
|
28
|
-
when: 'experimental.iconSet is written, or already names the
|
|
33
|
+
when: 'experimental.iconSet is written, or already names the sets',
|
|
29
34
|
},
|
|
30
35
|
{
|
|
31
36
|
code: 1,
|
|
32
|
-
when: '
|
|
37
|
+
when: 'a set is not installed, two packages ship it, a set is named twice, the config cannot be updated (not a static object literal, or it spreads another object, which you then edit by hand), or the config is protected (a symbolic link, or marked generated, vendored, or ignored: `ERR_CODEMOD_PROTECTED`, naming the declaration)',
|
|
33
38
|
},
|
|
34
39
|
],
|
|
35
40
|
related: ['experimental icon', 'template'],
|
|
@@ -57,9 +57,10 @@ function demoContentNote(count, file) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* The icon report as text lines (AST-084 FR10): one line per mapped
|
|
61
|
-
* icon, or one line saying why nothing was mapped.
|
|
62
|
-
* has no icon set at all (no report: icon sets are
|
|
60
|
+
* The icon report as text lines (AST-084 FR10): one line per mapped,
|
|
61
|
+
* placeholder, or kept icon, or one line saying why nothing was mapped.
|
|
62
|
+
* Nothing when the project has no icon set at all (no report: icon sets are
|
|
63
|
+
* experimental). A placeholder line names its meaning and both fixes.
|
|
63
64
|
* @param {import('../../../api/template/template.type.mjs').TemplateIconsReport | undefined} icons
|
|
64
65
|
* @returns {string[]}
|
|
65
66
|
*/
|
|
@@ -74,10 +75,18 @@ function iconLines(icons) {
|
|
|
74
75
|
...icons.mapped.map(
|
|
75
76
|
each =>
|
|
76
77
|
`icons: mapped ${each.source.export} (${each.source.module}) -> ${each.target.export} (${each.target.module}) by ${each.by}` +
|
|
78
|
+
(icons.order ? ` from ${each.target.package}/${each.target.set}` : '') +
|
|
79
|
+
(each.variant
|
|
80
|
+
? `; ${each.variant.asked} look not in the set, drew ${each.variant.used ?? 'its default look'}`
|
|
81
|
+
: '') +
|
|
77
82
|
(each.dropped && each.dropped.length > 0
|
|
78
83
|
? `; dropped ${each.dropped.join(', ')} (size comes from CSS)`
|
|
79
84
|
: ''),
|
|
80
85
|
),
|
|
86
|
+
...(icons.placeholder ?? []).map(
|
|
87
|
+
each =>
|
|
88
|
+
`icons: placeholder ${each.source.export} (${each.source.module}) -> ${each.target.local}: no set in your list has "${each.definitions.join('", "')}", so the file draws a placeholder it declares. Fix: add a backup set that has it, \`${each.fixes[1]}\`; or, if you author your main set, map it there, \`${each.fixes[0]}\`.`,
|
|
89
|
+
),
|
|
81
90
|
...icons.kept.map(
|
|
82
91
|
each =>
|
|
83
92
|
`icons: kept ${each.export}${each.module == null ? '' : ` (${each.module})`}: ${each.reason}` +
|
|
@@ -14,6 +14,20 @@ export function setConfigString(projectDir: string, key: string, value: string):
|
|
|
14
14
|
previous: string | null;
|
|
15
15
|
changed: boolean;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Set `key` to `value`, a string or a list of strings, in the project's
|
|
19
|
+
* astryx.config.
|
|
20
|
+
* @param {string} projectDir
|
|
21
|
+
* @param {string} key a top-level key, or a dotted path (`experimental.iconSet`)
|
|
22
|
+
* @param {string | readonly string[]} value
|
|
23
|
+
* @returns {{file: string, created: boolean, previous: string | string[] | null, changed: boolean}}
|
|
24
|
+
*/
|
|
25
|
+
export function setConfigValue(projectDir: string, key: string, value: string | readonly string[]): {
|
|
26
|
+
file: string;
|
|
27
|
+
created: boolean;
|
|
28
|
+
previous: string | string[] | null;
|
|
29
|
+
changed: boolean;
|
|
30
|
+
};
|
|
17
31
|
/** A protected astryx.config the writer refused to touch. */
|
|
18
32
|
export class ConfigProtectedError extends Error {
|
|
19
33
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file Write one string field of a project's
|
|
4
|
+
* @file Write one string field, or one list of strings, in a project's
|
|
5
|
+
* astryx.config.
|
|
5
6
|
*
|
|
6
7
|
* The field is a top-level key or a dotted path into nested object literals
|
|
7
8
|
* (`experimental.iconSet`); a missing object on the path is created.
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
* `ERR_CODEMOD_PROTECTED` naming the protecting declaration, and an unreadable
|
|
19
20
|
* protection source fails with `ERR_CODEMOD_PROTECTION_SOURCE`.
|
|
20
21
|
*
|
|
21
|
-
* @input A project directory, a field name, and its string
|
|
22
|
+
* @input A project directory, a field name, and its value: a string or a
|
|
23
|
+
* list of strings
|
|
22
24
|
* @output The config file written and the value it replaced
|
|
23
25
|
* @position packages/cli/foundation/config — read by `astryx experimental icon use`
|
|
24
26
|
*/
|
|
@@ -87,6 +89,47 @@ function propertyNamed(property, name) {
|
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
/** @param {string} text */
|
|
93
|
+
const quote = text => `'${text.replace(/\\/gu, '\\\\').replace(/'/gu, "\\'")}'`;
|
|
94
|
+
|
|
95
|
+
/** @param {string | readonly string[]} value */
|
|
96
|
+
const written = value =>
|
|
97
|
+
typeof value === 'string'
|
|
98
|
+
? quote(value)
|
|
99
|
+
: `[${value.map(each => quote(each)).join(', ')}]`;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The string, or list of strings, a config property holds; null for any
|
|
103
|
+
* other value.
|
|
104
|
+
* @param {any} node
|
|
105
|
+
* @returns {string | string[] | null}
|
|
106
|
+
*/
|
|
107
|
+
function literalValue(node) {
|
|
108
|
+
if (node?.type === 'StringLiteral' || node?.type === 'Literal') {
|
|
109
|
+
return typeof node.value === 'string' ? node.value : null;
|
|
110
|
+
}
|
|
111
|
+
if (node?.type === 'ArrayExpression') {
|
|
112
|
+
const items = node.elements.map((/** @type {any} */ each) =>
|
|
113
|
+
each?.type === 'StringLiteral' ||
|
|
114
|
+
(each?.type === 'Literal' && typeof each.value === 'string')
|
|
115
|
+
? String(each.value)
|
|
116
|
+
: null,
|
|
117
|
+
);
|
|
118
|
+
return items.every((/** @type {string | null} */ each) => each != null)
|
|
119
|
+
? /** @type {string[]} */ (items)
|
|
120
|
+
: null;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string | string[] | null} a
|
|
127
|
+
* @param {string | readonly string[]} b
|
|
128
|
+
*/
|
|
129
|
+
function sameValue(a, b) {
|
|
130
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
/**
|
|
91
134
|
* Set `key` to `value` in the project's astryx.config.
|
|
92
135
|
* @param {string} projectDir
|
|
@@ -95,6 +138,22 @@ function propertyNamed(property, name) {
|
|
|
95
138
|
* @returns {{file: string, created: boolean, previous: string | null, changed: boolean}}
|
|
96
139
|
*/
|
|
97
140
|
export function setConfigString(projectDir, key, value) {
|
|
141
|
+
const result = setConfigValue(projectDir, key, value);
|
|
142
|
+
return {
|
|
143
|
+
...result,
|
|
144
|
+
previous: typeof result.previous === 'string' ? result.previous : null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Set `key` to `value`, a string or a list of strings, in the project's
|
|
150
|
+
* astryx.config.
|
|
151
|
+
* @param {string} projectDir
|
|
152
|
+
* @param {string} key a top-level key, or a dotted path (`experimental.iconSet`)
|
|
153
|
+
* @param {string | readonly string[]} value
|
|
154
|
+
* @returns {{file: string, created: boolean, previous: string | string[] | null, changed: boolean}}
|
|
155
|
+
*/
|
|
156
|
+
export function setConfigValue(projectDir, key, value) {
|
|
98
157
|
const keys = key.split('.');
|
|
99
158
|
const leaf = /** @type {string} */ (keys[keys.length - 1]);
|
|
100
159
|
const existing = CONFIG_BASENAMES.map(name =>
|
|
@@ -103,7 +162,6 @@ export function setConfigString(projectDir, key, value) {
|
|
|
103
162
|
if (!existing) {
|
|
104
163
|
const file = path.join(projectDir, 'astryx.config.mjs');
|
|
105
164
|
assertWritable(projectDir, file);
|
|
106
|
-
const quoted = `'${value.replace(/\\/gu, '\\\\').replace(/'/gu, "\\'")}'`;
|
|
107
165
|
// Each key on the path opens one nested object: `experimental: {`.
|
|
108
166
|
const open = keys
|
|
109
167
|
.slice(0, -1)
|
|
@@ -116,7 +174,7 @@ export function setConfigString(projectDir, key, value) {
|
|
|
116
174
|
.join('');
|
|
117
175
|
writeAtomically(
|
|
118
176
|
file,
|
|
119
|
-
`export default {\n${open}${' '.repeat(keys.length)}${leaf}: ${
|
|
177
|
+
`export default {\n${open}${' '.repeat(keys.length)}${leaf}: ${written(value)},\n${close}};\n`,
|
|
120
178
|
);
|
|
121
179
|
return {file, created: true, previous: null, changed: true};
|
|
122
180
|
}
|
|
@@ -127,7 +185,7 @@ export function setConfigString(projectDir, key, value) {
|
|
|
127
185
|
let object = findManifestObject(j, ast);
|
|
128
186
|
const refuse = () =>
|
|
129
187
|
new Error(
|
|
130
|
-
`Could not update ${path.basename(existing)} safely: its default export is not a static object literal. Set "${key}:
|
|
188
|
+
`Could not update ${path.basename(existing)} safely: its default export is not a static object literal. Set "${key}: ${written(value)}" in it yourself.`,
|
|
131
189
|
);
|
|
132
190
|
if (!object) throw refuse();
|
|
133
191
|
// A spread on the path can hold the same keys: a key added beside it would
|
|
@@ -137,7 +195,7 @@ export function setConfigString(projectDir, key, value) {
|
|
|
137
195
|
const assertNoSpread = (holder, where) => {
|
|
138
196
|
if (holder.properties.some((/** @type {any} */ each) => isSpread(each))) {
|
|
139
197
|
throw new Error(
|
|
140
|
-
`Could not update ${path.basename(existing)} safely: ${where} spreads another object (\`...\`), whose keys a written "${key}" could replace. Set "${key}:
|
|
198
|
+
`Could not update ${path.basename(existing)} safely: ${where} spreads another object (\`...\`), whose keys a written "${key}" could replace. Set "${key}: ${written(value)}" in it yourself.`,
|
|
141
199
|
);
|
|
142
200
|
}
|
|
143
201
|
};
|
|
@@ -160,22 +218,20 @@ export function setConfigString(projectDir, key, value) {
|
|
|
160
218
|
const property = object.properties.find((/** @type {any} */ each) =>
|
|
161
219
|
propertyNamed(each, leaf),
|
|
162
220
|
);
|
|
163
|
-
|
|
221
|
+
const node =
|
|
222
|
+
typeof value === 'string'
|
|
223
|
+
? j.stringLiteral(value)
|
|
224
|
+
: j.arrayExpression(value.map(each => j.stringLiteral(each)));
|
|
225
|
+
/** @type {string | string[] | null} */
|
|
164
226
|
let previous = null;
|
|
165
227
|
if (property) {
|
|
166
|
-
|
|
167
|
-
previous
|
|
168
|
-
current?.type === 'StringLiteral' || current?.type === 'Literal'
|
|
169
|
-
? String(current.value)
|
|
170
|
-
: null;
|
|
171
|
-
if (previous === value) {
|
|
228
|
+
previous = literalValue(property.value);
|
|
229
|
+
if (sameValue(previous, value)) {
|
|
172
230
|
return {file: existing, created: false, previous, changed: false};
|
|
173
231
|
}
|
|
174
|
-
property.value =
|
|
232
|
+
property.value = node;
|
|
175
233
|
} else {
|
|
176
|
-
object.properties.push(
|
|
177
|
-
j.objectProperty(j.identifier(leaf), j.stringLiteral(value)),
|
|
178
|
-
);
|
|
234
|
+
object.properties.push(j.objectProperty(j.identifier(leaf), node));
|
|
179
235
|
}
|
|
180
236
|
let next = ast.toSource({quote: 'single', reuseWhitespace: true});
|
|
181
237
|
if (source.endsWith('\n') && !next.endsWith('\n')) next += '\n';
|
|
@@ -43,6 +43,14 @@ export function hasLicenseNotice(dir: string): boolean;
|
|
|
43
43
|
* file is invalid; `unresolved`: its module does not resolve from the project
|
|
44
44
|
* @property {string} reason
|
|
45
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* One backup set the project's list names, and what it resolves to.
|
|
48
|
+
* @typedef {object} IconSetBackup
|
|
49
|
+
* @property {string} selector the name as the list writes it
|
|
50
|
+
* @property {'selected' | 'ambiguous' | 'missing' | 'withdrawn'} status
|
|
51
|
+
* @property {DiscoveredIconSet} [set] the set, when selected
|
|
52
|
+
* @property {string} reason one sentence saying why
|
|
53
|
+
*/
|
|
46
54
|
/**
|
|
47
55
|
* The project's icon set (FR8).
|
|
48
56
|
* @typedef {object} IconSetChoice
|
|
@@ -53,7 +61,13 @@ export function hasLicenseNotice(dir: string): boolean;
|
|
|
53
61
|
* `withdrawn`: it names a set that is installed but withdrawn (invalid, or
|
|
54
62
|
* its module does not resolve), and `reason` says why
|
|
55
63
|
* @property {DiscoveredIconSet} [set] the project's set, when selected
|
|
56
|
-
* @property {string} [configured] the `experimental.iconSet`
|
|
64
|
+
* @property {string} [configured] the main set `experimental.iconSet` names
|
|
65
|
+
* (its first entry, when it is a list), when set
|
|
66
|
+
* @property {IconSetBackup[]} [backups] the backup sets the list names after
|
|
67
|
+
* the main set, in order, when it names any
|
|
68
|
+
* @property {DiscoveredIconSet[]} [order] the sets a template icon is looked
|
|
69
|
+
* up in, in order: the main set, then each backup that resolves. Present
|
|
70
|
+
* only when the main set is selected
|
|
57
71
|
* @property {string} reason one sentence saying why
|
|
58
72
|
*/
|
|
59
73
|
/**
|
|
@@ -120,16 +134,39 @@ export function discoverIntegrationIconSets(integration: import("../integrations
|
|
|
120
134
|
*/
|
|
121
135
|
export function withdrawnNamed(withdrawn: WithdrawnIconSet[], selector: string): WithdrawnIconSet[];
|
|
122
136
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
137
|
+
* The entries of an `experimental.iconSet` value, main set first: a string is
|
|
138
|
+
* a one-entry list.
|
|
139
|
+
* @param {string | readonly string[] | undefined} configured
|
|
140
|
+
* @returns {string[]}
|
|
141
|
+
*/
|
|
142
|
+
export function iconSetList(configured: string | readonly string[] | undefined): string[];
|
|
143
|
+
/**
|
|
144
|
+
* Installed sets the project's list leaves out: the problem, and the fix that
|
|
145
|
+
* adds them as backups after the list's own. Doctor and `experimental icon
|
|
146
|
+
* use` print the same words.
|
|
147
|
+
* @param {number} installed how many sets are installed
|
|
148
|
+
* @param {string} main the main set as the list writes it
|
|
149
|
+
* @param {string[]} backups the list's backups, in order
|
|
150
|
+
* @param {string[]} unlisted the installed sets the list leaves out
|
|
151
|
+
* @returns {{problem: string, command: string, fix: string}}
|
|
152
|
+
*/
|
|
153
|
+
export function unlistedIconSets(installed: number, main: string, backups: string[], unlisted: string[]): {
|
|
154
|
+
problem: string;
|
|
155
|
+
command: string;
|
|
156
|
+
fix: string;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Pick the project's icon set (FR8, FR17): the main set `experimental.iconSet`
|
|
160
|
+
* names, as `"<set>"` or `"<package>/<set>"`, or as the first entry of a list
|
|
161
|
+
* whose other entries are backup sets in order; otherwise the only installed
|
|
125
162
|
* set; otherwise none. A configured name that matches no valid set but an
|
|
126
163
|
* installed set that was withdrawn says so, with why.
|
|
127
164
|
* @param {DiscoveredIconSet[]} sets installed sets, in integration load order
|
|
128
|
-
* @param {string | undefined} configured the `experimental.iconSet` config value
|
|
165
|
+
* @param {string | readonly string[] | undefined} configured the `experimental.iconSet` config value
|
|
129
166
|
* @param {WithdrawnIconSet[]} [withdrawn] installed sets that were withdrawn
|
|
130
167
|
* @returns {IconSetChoice}
|
|
131
168
|
*/
|
|
132
|
-
export function chooseProjectIconSet(sets: DiscoveredIconSet[], configured: string | undefined, withdrawn?: WithdrawnIconSet[]): IconSetChoice;
|
|
169
|
+
export function chooseProjectIconSet(sets: DiscoveredIconSet[], configured: string | readonly string[] | undefined, withdrawn?: WithdrawnIconSet[]): IconSetChoice;
|
|
133
170
|
/**
|
|
134
171
|
* A set's full name, `<package>/<set>`.
|
|
135
172
|
* @param {{package: string, name: string}} set
|
|
@@ -139,7 +176,8 @@ export function iconSetKey(set: {
|
|
|
139
176
|
name: string;
|
|
140
177
|
}): string;
|
|
141
178
|
/**
|
|
142
|
-
* Sets in tie order (FR6, FR8): the project's set first,
|
|
179
|
+
* Sets in tie order (FR6, FR8): the project's set and its backups first, in
|
|
180
|
+
* list order, then load order.
|
|
143
181
|
* @param {DiscoveredIconSet[]} sets
|
|
144
182
|
* @param {IconSetChoice} choice
|
|
145
183
|
* @returns {DiscoveredIconSet[]}
|
|
@@ -147,6 +185,8 @@ export function iconSetKey(set: {
|
|
|
147
185
|
export function orderIconSets(sets: DiscoveredIconSet[], choice: IconSetChoice): DiscoveredIconSet[];
|
|
148
186
|
/** A license or notice file a set ships beside its data (FR15). */
|
|
149
187
|
export const NOTICE_FILE_RE: RegExp;
|
|
188
|
+
/** The command that chooses a main set and backups, as the CLI prints it. */
|
|
189
|
+
export const ICON_USE_ORDER_COMMAND: "astryx experimental icon use <main> --backup <set>";
|
|
150
190
|
/**
|
|
151
191
|
* One installed icon set: its descriptor, its data, and where it came from.
|
|
152
192
|
*/
|
|
@@ -213,6 +253,24 @@ export type WithdrawnIconSet = {
|
|
|
213
253
|
kind: "invalid" | "unresolved";
|
|
214
254
|
reason: string;
|
|
215
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* One backup set the project's list names, and what it resolves to.
|
|
258
|
+
*/
|
|
259
|
+
export type IconSetBackup = {
|
|
260
|
+
/**
|
|
261
|
+
* the name as the list writes it
|
|
262
|
+
*/
|
|
263
|
+
selector: string;
|
|
264
|
+
status: "selected" | "ambiguous" | "missing" | "withdrawn";
|
|
265
|
+
/**
|
|
266
|
+
* the set, when selected
|
|
267
|
+
*/
|
|
268
|
+
set?: DiscoveredIconSet | undefined;
|
|
269
|
+
/**
|
|
270
|
+
* one sentence saying why
|
|
271
|
+
*/
|
|
272
|
+
reason: string;
|
|
273
|
+
};
|
|
216
274
|
/**
|
|
217
275
|
* The project's icon set (FR8).
|
|
218
276
|
*/
|
|
@@ -230,9 +288,21 @@ export type IconSetChoice = {
|
|
|
230
288
|
*/
|
|
231
289
|
set?: DiscoveredIconSet | undefined;
|
|
232
290
|
/**
|
|
233
|
-
* the `experimental.iconSet`
|
|
291
|
+
* the main set `experimental.iconSet` names
|
|
292
|
+
* (its first entry, when it is a list), when set
|
|
234
293
|
*/
|
|
235
294
|
configured?: string | undefined;
|
|
295
|
+
/**
|
|
296
|
+
* the backup sets the list names after
|
|
297
|
+
* the main set, in order, when it names any
|
|
298
|
+
*/
|
|
299
|
+
backups?: IconSetBackup[] | undefined;
|
|
300
|
+
/**
|
|
301
|
+
* the sets a template icon is looked
|
|
302
|
+
* up in, in order: the main set, then each backup that resolves. Present
|
|
303
|
+
* only when the main set is selected
|
|
304
|
+
*/
|
|
305
|
+
order?: DiscoveredIconSet[] | undefined;
|
|
236
306
|
/**
|
|
237
307
|
* one sentence saying why
|
|
238
308
|
*/
|