@lablup/ui-common-cli 0.2.0-alpha.19.pr-73-20261022061203-8f47fc7d0 → 0.2.0-alpha.20
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 +2 -2
- package/cli/adopt.mjs +22 -0
- package/cli/main.mjs +18 -1
- package/cli/rewrite.mjs +4 -3
- package/cli/user-skill.mjs +340 -0
- package/codemods/0.2/components.mjs +1 -1
- package/codemods/0.2/elements.mjs +3 -3
- package/codemods/0.2/index.mjs +1 -1
- package/codemods/0.2/package-json.mjs +6 -4
- package/codemods/0.2/stylesheets.mjs +2 -2
- package/codemods/0.2.0-alpha.20/drawer.mjs +203 -0
- package/codemods/0.2.0-alpha.20/index.mjs +24 -0
- package/codemods/0.2.0-alpha.20/upstream.json +19 -0
- package/codemods/adopt/scan.mjs +1 -1
- package/codemods/registry.mjs +6 -1
- package/migration/0.1-to-0.2.json +2 -3
- package/package.json +4 -4
- package/skill/ui-common/SKILL.md +19 -0
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
The `ui-common` command line for [`@lablup/ui-common`](https://www.npmjs.com/package/@lablup/ui-common):
|
|
4
4
|
the Astryx CLI that ui-common pins, with its output in `@lablup/ui-common`
|
|
5
5
|
terms, the agent block, the upgrade codemods, `adopt --from astryx` for an
|
|
6
|
-
app that uses Astryx directly, `doctor`,
|
|
7
|
-
skill.
|
|
6
|
+
app that uses Astryx directly, `doctor`, the `ui-common-adopt` agent skill,
|
|
7
|
+
and the computer-level `ui-common` skill (`ui-common init --user`).
|
|
8
8
|
|
|
9
9
|
It is a separate package so the CLI's toolchain (the Astryx CLI, jscodeshift,
|
|
10
10
|
postcss) stays out of a consumer's production install. It is released at the
|
package/cli/adopt.mjs
CHANGED
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
addLabOverride,
|
|
28
28
|
} from "../codemods/0.2/package-json.mjs";
|
|
29
29
|
import { DIALOG_NAMES, transformDialogs } from "../codemods/adopt/dialog.mjs";
|
|
30
|
+
import {
|
|
31
|
+
LAB as LAB_MIRROR,
|
|
32
|
+
moveDrawerImports,
|
|
33
|
+
} from "../codemods/0.2.0-alpha.20/drawer.mjs";
|
|
30
34
|
import {
|
|
31
35
|
addUiCommonLayer,
|
|
32
36
|
ensureHtmlLayerStatement,
|
|
@@ -286,6 +290,24 @@ export async function runAdopt(options) {
|
|
|
286
290
|
},
|
|
287
291
|
);
|
|
288
292
|
touch(file, rewritten.text, "specifiers");
|
|
293
|
+
// lab's Drawer is core's since Astryx 0.7.2, so ui-common's lives at
|
|
294
|
+
// /Drawer, not in the lab mirror the specifier rewrite points at.
|
|
295
|
+
if (script && entry.current.includes(LAB_MIRROR)) {
|
|
296
|
+
const j = jscodeshift.withParser(/\.[cm]?tsx?$/.test(ext) ? "tsx" : "babel");
|
|
297
|
+
try {
|
|
298
|
+
const out = moveDrawerImports(
|
|
299
|
+
{ source: entry.current, path: file },
|
|
300
|
+
{ jscodeshift: j },
|
|
301
|
+
);
|
|
302
|
+
if (out != null) touch(file, out, "drawer-from-lab");
|
|
303
|
+
} catch (err) {
|
|
304
|
+
errors.push({
|
|
305
|
+
file: rel(file),
|
|
306
|
+
transform: "drawer-from-lab",
|
|
307
|
+
error: `could not be parsed, so Drawer imports were left in ${LAB_MIRROR}: ${/** @type {Error} */ (err).message}`,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
289
311
|
for (const [row, n] of Object.entries(rewritten.rows))
|
|
290
312
|
rows[row] = (rows[row] ?? 0) + n;
|
|
291
313
|
for (const l of rewritten.left) {
|
package/cli/main.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { TEMPLATE_HELP, templateCommand } from "./templates.mjs";
|
|
|
16
16
|
import { UPGRADE_HELP, upgradeCommand } from "./upgrade.mjs";
|
|
17
17
|
import { ADOPT_HELP, adoptCommand } from "./adopt.mjs";
|
|
18
18
|
import { DOCTOR_HELP, doctorCommand } from "./doctor.mjs";
|
|
19
|
+
import { INIT_USER_HELP, initUserCommand } from "./user-skill.mjs";
|
|
19
20
|
|
|
20
21
|
export const HELP = `Usage: ui-common <command> [options]
|
|
21
22
|
|
|
@@ -39,6 +40,10 @@ ui-common commands:
|
|
|
39
40
|
agents [--write <file>] [--check] [--skill [--dir <dir>]]
|
|
40
41
|
Print, write or check the ui-common agent block (UI-COMMON markers);
|
|
41
42
|
install the ui-common-adopt agent skill.
|
|
43
|
+
init --user [--remove-agents]
|
|
44
|
+
Install the ui-common agent skill for every agent on this computer
|
|
45
|
+
(~/.agents/skills/ui-common, linked into ~/.claude/skills and the
|
|
46
|
+
like), in place of Astryx's. Any other \`init\` is Astryx's.
|
|
42
47
|
template [<name> [<path>]] [--list] [--type page|block] [--overwrite]
|
|
43
48
|
List, print or scaffold one of ui-common's page and block templates.
|
|
44
49
|
Astryx's own templates are not offered; \`search\` and \`build\` leave
|
|
@@ -98,6 +103,10 @@ export async function main(argv) {
|
|
|
98
103
|
return 0;
|
|
99
104
|
case "agents":
|
|
100
105
|
return agentsCommand(rest);
|
|
106
|
+
case "init":
|
|
107
|
+
// The computer-level skill is ui-common's own; a project init is Astryx's.
|
|
108
|
+
if (rest.includes("--user")) return initUserCommand(rest);
|
|
109
|
+
return passthrough(argv);
|
|
101
110
|
case "template":
|
|
102
111
|
return templateCommand(rest);
|
|
103
112
|
case "upgrade":
|
|
@@ -117,4 +126,12 @@ export async function main(argv) {
|
|
|
117
126
|
}
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
export {
|
|
129
|
+
export {
|
|
130
|
+
ADOPT_HELP,
|
|
131
|
+
AGENTS_HELP,
|
|
132
|
+
DOCTOR_HELP,
|
|
133
|
+
INIT_USER_HELP,
|
|
134
|
+
SYNC_HELP,
|
|
135
|
+
TEMPLATE_HELP,
|
|
136
|
+
UPGRADE_HELP,
|
|
137
|
+
};
|
package/cli/rewrite.mjs
CHANGED
|
@@ -69,10 +69,11 @@ export function rewriteCommands(text, invocation = "ui-common") {
|
|
|
69
69
|
const commands = ASTRYX_COMMANDS.join("|");
|
|
70
70
|
return (
|
|
71
71
|
text
|
|
72
|
-
// A one-off `npx @astryxdesign/cli` invocation
|
|
73
|
-
//
|
|
72
|
+
// A one-off `npx @astryxdesign/cli` invocation (since Astryx 0.7.2
|
|
73
|
+
// pinned to a release, `npx @astryxdesign/cli@0.7.3`, when no project
|
|
74
|
+
// CLI answers), or an installed-bin invocation through a package manager.
|
|
74
75
|
.replace(
|
|
75
|
-
/\b(?:npx
|
|
76
|
+
/\b(?:npx(?: -y)?|pnpm dlx|yarn dlx|bunx) @astryxdesign\/cli(?:@[\w.+-]+)?(?=[\s`'"]|$)/g,
|
|
76
77
|
invocation,
|
|
77
78
|
)
|
|
78
79
|
.replace(
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ui-common init --user`: the computer-level agent skill, ui-common's
|
|
3
|
+
* counterpart of the one `astryx init --user` writes (Astryx 0.7.2).
|
|
4
|
+
*
|
|
5
|
+
* Astryx's skill tells every agent on the computer to run `astryx` and add
|
|
6
|
+
* `@astryxdesign/core`; in a project that depends on @lablup/ui-common that
|
|
7
|
+
* is the wrong advice. ui-common writes its own instead, from
|
|
8
|
+
* `skill/ui-common/SKILL.md`, at `~/.agents/skills/ui-common/SKILL.md`, and
|
|
9
|
+
* links its folder into the skill folders Astryx links its own into (the
|
|
10
|
+
* table is Astryx's, `SKILL_LINKS`, so the two reach the same agents):
|
|
11
|
+
* `~/.claude/skills` always, the others when their agent is installed, and a
|
|
12
|
+
* managed copy where the agent skips links.
|
|
13
|
+
*
|
|
14
|
+
* The written file carries a hash line, so a file a person edited, a file
|
|
15
|
+
* ui-common did not write, a symlinked file, and a link target holding
|
|
16
|
+
* anything else are never changed or removed. The text is version-free, so
|
|
17
|
+
* nothing refreshes it in the background; running the command again after
|
|
18
|
+
* an upgrade does.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import {
|
|
22
|
+
existsSync,
|
|
23
|
+
lstatSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
readdirSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
readlinkSync,
|
|
28
|
+
realpathSync,
|
|
29
|
+
rmdirSync,
|
|
30
|
+
symlinkSync,
|
|
31
|
+
unlinkSync,
|
|
32
|
+
writeFileSync,
|
|
33
|
+
} from "node:fs";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
36
|
+
|
|
37
|
+
import { CLI_ROOT, importAstryxInternal } from "./paths.mjs";
|
|
38
|
+
|
|
39
|
+
export const USER_SKILL_NAME = "ui-common";
|
|
40
|
+
export const USER_SKILL_SOURCE = join(CLI_ROOT, "skill", USER_SKILL_NAME, "SKILL.md");
|
|
41
|
+
|
|
42
|
+
/** The skill folder, relative to the home folder: where Astryx keeps its own. */
|
|
43
|
+
const SKILL_FOLDER = join(".agents", "skills", USER_SKILL_NAME);
|
|
44
|
+
/** Astryx's skill file, which the receipt mentions when it is there too. */
|
|
45
|
+
const ASTRYX_SKILL = join(".agents", "skills", "astryx", "SKILL.md");
|
|
46
|
+
|
|
47
|
+
const HASH_LINE = /^<!-- ui-common-managed sha256:([0-9a-f]{64}) -->\n/m;
|
|
48
|
+
|
|
49
|
+
/** @param {string} text */
|
|
50
|
+
const sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The file `init --user` writes: the shipped skill with a hash line after its
|
|
54
|
+
* frontmatter, so a later run tells an unedited file from an edited one.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} [source] the skill text
|
|
57
|
+
*/
|
|
58
|
+
export function renderUserSkill(source = readFileSync(USER_SKILL_SOURCE, "utf8")) {
|
|
59
|
+
const end = source.indexOf("\n---\n", 4);
|
|
60
|
+
if (!source.startsWith("---\n") || end === -1) {
|
|
61
|
+
throw new Error(`${USER_SKILL_SOURCE} has no frontmatter`);
|
|
62
|
+
}
|
|
63
|
+
const cut = end + "\n---\n".length;
|
|
64
|
+
return `${source.slice(0, cut)}<!-- ui-common-managed sha256:${sha256(source)} -->\n${source.slice(cut)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* - `absent`: nothing there.
|
|
69
|
+
* - `current`: exactly this release's rendering.
|
|
70
|
+
* - `differs`: an unedited file ui-common wrote, from another release.
|
|
71
|
+
* - `edited`: a file ui-common wrote that a person changed. Never touched.
|
|
72
|
+
* - `foreign`: no hash line, a symlink, or not a regular file. Never touched.
|
|
73
|
+
*
|
|
74
|
+
* @typedef {'absent' | 'current' | 'differs' | 'edited' | 'foreign'} FileState
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} file
|
|
79
|
+
* @param {string} expected
|
|
80
|
+
* @returns {FileState}
|
|
81
|
+
*/
|
|
82
|
+
function fileState(file, expected) {
|
|
83
|
+
let stat;
|
|
84
|
+
try {
|
|
85
|
+
stat = lstatSync(file);
|
|
86
|
+
} catch {
|
|
87
|
+
return "absent";
|
|
88
|
+
}
|
|
89
|
+
if (!stat.isFile()) return "foreign";
|
|
90
|
+
const content = readFileSync(file, "utf8");
|
|
91
|
+
const match = HASH_LINE.exec(content);
|
|
92
|
+
if (!match) return "foreign";
|
|
93
|
+
if (content === expected) return "current";
|
|
94
|
+
return sha256(content.replace(HASH_LINE, "")) === match[1] ? "differs" : "edited";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Where a link sits: `linked` (a link to the skill folder), `absent`, a
|
|
99
|
+
* managed copy (`copy`, unedited) or `foreign` (anything else; never touched).
|
|
100
|
+
*
|
|
101
|
+
* @param {string} at
|
|
102
|
+
* @param {string} folder
|
|
103
|
+
*/
|
|
104
|
+
function linkState(at, folder) {
|
|
105
|
+
let stat;
|
|
106
|
+
try {
|
|
107
|
+
stat = lstatSync(at);
|
|
108
|
+
} catch {
|
|
109
|
+
return "absent";
|
|
110
|
+
}
|
|
111
|
+
if (stat.isSymbolicLink()) {
|
|
112
|
+
const target = resolve(dirname(at), readlinkSync(at));
|
|
113
|
+
let real = target;
|
|
114
|
+
try {
|
|
115
|
+
real = realpathSync(target);
|
|
116
|
+
} catch {
|
|
117
|
+
// dangling: compare the target as written
|
|
118
|
+
}
|
|
119
|
+
let ours = folder;
|
|
120
|
+
try {
|
|
121
|
+
ours = realpathSync(folder);
|
|
122
|
+
} catch {
|
|
123
|
+
// the skill folder is gone
|
|
124
|
+
}
|
|
125
|
+
return real === ours || target === folder ? "linked" : "foreign";
|
|
126
|
+
}
|
|
127
|
+
if (!stat.isDirectory()) return "foreign";
|
|
128
|
+
const entries = readdirSync(at);
|
|
129
|
+
if (entries.length !== 1 || entries[0] !== "SKILL.md") return "foreign";
|
|
130
|
+
const state = fileState(join(at, "SKILL.md"), "");
|
|
131
|
+
return state === "differs" ? "copy" : "foreign";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @param {string} at @param {string} folder */
|
|
135
|
+
function makeLink(at, folder) {
|
|
136
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
137
|
+
if (process.platform === "win32") {
|
|
138
|
+
symlinkSync(folder, at, "junction");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const real = realpathSync(folder);
|
|
142
|
+
const rel = relative(realpathSync(dirname(at)), real);
|
|
143
|
+
symlinkSync(isAbsolute(rel) ? real : rel, at, "dir");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @param {string} file */
|
|
147
|
+
function removeFile(file) {
|
|
148
|
+
unlinkSync(file);
|
|
149
|
+
try {
|
|
150
|
+
rmdirSync(dirname(file));
|
|
151
|
+
} catch {
|
|
152
|
+
// the folder holds something else
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** `~/.claude/skills/ui-common` for a path in `home`. @param {string} file @param {string} home */
|
|
157
|
+
export function displayPath(file, home) {
|
|
158
|
+
const rel = relative(home, file);
|
|
159
|
+
return rel && !rel.startsWith("..") && !isAbsolute(rel) ? `~${sep}${rel}` : file;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The skill folders to link, from Astryx's table, for the ui-common skill.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} home
|
|
166
|
+
* @returns {Promise<Array<{path: string, wanted: boolean, mode: 'link' | 'copy'}>>}
|
|
167
|
+
*/
|
|
168
|
+
export async function userSkillLinks(home) {
|
|
169
|
+
const { SKILL_LINKS } = await importAstryxInternal(
|
|
170
|
+
"foundation/agent-docs/computer-agent-text.mjs",
|
|
171
|
+
);
|
|
172
|
+
return /** @type {Array<{skills: string, when: string | null, mode: 'link' | 'copy'}>} */ (
|
|
173
|
+
SKILL_LINKS
|
|
174
|
+
).map(({ skills, when, mode }) => ({
|
|
175
|
+
path: join(home, skills, USER_SKILL_NAME),
|
|
176
|
+
wanted: when == null || existsSync(join(home, when)),
|
|
177
|
+
mode,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @typedef {{path: string, action: 'written' | 'unchanged' | 'linked' | 'removed' | 'absent' | 'kept', reason?: string}} Row
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Write or refresh the skill and link it where agents read it.
|
|
187
|
+
*
|
|
188
|
+
* @param {{home?: string, source?: string}} [options]
|
|
189
|
+
* @returns {Promise<Row[]>}
|
|
190
|
+
*/
|
|
191
|
+
export async function writeUserSkill({ home = homedir(), source } = {}) {
|
|
192
|
+
const expected = renderUserSkill(source);
|
|
193
|
+
const folder = join(home, SKILL_FOLDER);
|
|
194
|
+
const file = join(folder, "SKILL.md");
|
|
195
|
+
/** @type {Row[]} */
|
|
196
|
+
const rows = [];
|
|
197
|
+
const state = fileState(file, expected);
|
|
198
|
+
if (state === "current") rows.push({ path: file, action: "unchanged" });
|
|
199
|
+
else if (state === "absent" || state === "differs") {
|
|
200
|
+
mkdirSync(folder, { recursive: true });
|
|
201
|
+
writeFileSync(file, expected);
|
|
202
|
+
rows.push({ path: file, action: "written" });
|
|
203
|
+
} else {
|
|
204
|
+
rows.push({ path: file, action: "kept", reason: state });
|
|
205
|
+
return rows;
|
|
206
|
+
}
|
|
207
|
+
for (const place of await userSkillLinks(home)) {
|
|
208
|
+
if (!place.wanted) continue;
|
|
209
|
+
const at = place.path;
|
|
210
|
+
const now = linkState(at, folder);
|
|
211
|
+
if (now === "linked") {
|
|
212
|
+
rows.push({ path: at, action: "unchanged" });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (now === "foreign") {
|
|
216
|
+
rows.push({ path: at, action: "kept", reason: "foreign" });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (place.mode === "link") {
|
|
220
|
+
if (now === "copy") removeFile(join(at, "SKILL.md"));
|
|
221
|
+
try {
|
|
222
|
+
makeLink(at, folder);
|
|
223
|
+
rows.push({ path: at, action: "linked" });
|
|
224
|
+
continue;
|
|
225
|
+
} catch {
|
|
226
|
+
// no links on this file system: a managed copy instead
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const copy = join(at, "SKILL.md");
|
|
230
|
+
if (fileState(copy, expected) === "current") {
|
|
231
|
+
rows.push({ path: at, action: "unchanged" });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
mkdirSync(at, { recursive: true });
|
|
235
|
+
writeFileSync(copy, expected);
|
|
236
|
+
rows.push({ path: at, action: "written" });
|
|
237
|
+
}
|
|
238
|
+
return rows;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Remove the skill, its links and its managed copies; anything ui-common did
|
|
243
|
+
* not write, or a person edited, stays.
|
|
244
|
+
*
|
|
245
|
+
* @param {{home?: string}} [options]
|
|
246
|
+
* @returns {Promise<Row[]>}
|
|
247
|
+
*/
|
|
248
|
+
export async function removeUserSkill({ home = homedir() } = {}) {
|
|
249
|
+
const folder = join(home, SKILL_FOLDER);
|
|
250
|
+
const file = join(folder, "SKILL.md");
|
|
251
|
+
const state = fileState(file, "");
|
|
252
|
+
if (state === "absent") return [{ path: file, action: "absent" }];
|
|
253
|
+
// An edited or foreign file stays, and so do the links that reach it.
|
|
254
|
+
if (state !== "differs") return [{ path: file, action: "kept", reason: state }];
|
|
255
|
+
/** @type {Row[]} */
|
|
256
|
+
const rows = [];
|
|
257
|
+
for (const place of await userSkillLinks(home)) {
|
|
258
|
+
const now = linkState(place.path, folder);
|
|
259
|
+
if (now === "linked") unlinkSync(place.path);
|
|
260
|
+
else if (now === "copy") removeFile(join(place.path, "SKILL.md"));
|
|
261
|
+
else continue;
|
|
262
|
+
rows.push({ path: place.path, action: "removed" });
|
|
263
|
+
}
|
|
264
|
+
removeFile(file);
|
|
265
|
+
rows.unshift({ path: file, action: "removed" });
|
|
266
|
+
return rows;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const VERBS = { written: "Wrote", linked: "Linked", removed: "Removed" };
|
|
270
|
+
|
|
271
|
+
const KEPT = {
|
|
272
|
+
edited: "edited by hand, left as it is",
|
|
273
|
+
foreign: "not written by ui-common, left as it is",
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {string[]} argv arguments after `init`, `--user` included
|
|
278
|
+
* @param {{home?: string, out?: (s: string) => void, err?: (s: string) => void}} [io]
|
|
279
|
+
* @returns {Promise<number>}
|
|
280
|
+
*/
|
|
281
|
+
export async function initUserCommand(argv, io = {}) {
|
|
282
|
+
const home = io.home ?? homedir();
|
|
283
|
+
const out = io.out ?? ((s) => process.stdout.write(s));
|
|
284
|
+
const err = io.err ?? ((s) => process.stderr.write(s));
|
|
285
|
+
let remove = false;
|
|
286
|
+
for (const arg of argv) {
|
|
287
|
+
if (arg === "--user") continue;
|
|
288
|
+
if (arg === "--remove-agents") remove = true;
|
|
289
|
+
else if (arg === "-h" || arg === "--help") {
|
|
290
|
+
out(INIT_USER_HELP);
|
|
291
|
+
return 0;
|
|
292
|
+
} else {
|
|
293
|
+
err(`ui-common init --user: unknown argument "${arg}"\n${INIT_USER_HELP}`);
|
|
294
|
+
return 2;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const rows = remove
|
|
298
|
+
? await removeUserSkill({ home })
|
|
299
|
+
: await writeUserSkill({ home });
|
|
300
|
+
for (const row of rows) {
|
|
301
|
+
const where = displayPath(row.path, home);
|
|
302
|
+
const line =
|
|
303
|
+
row.action === "kept"
|
|
304
|
+
? `[keep] ${where}: ${KEPT[/** @type {'edited' | 'foreign'} */ (row.reason)] ?? row.reason}`
|
|
305
|
+
: row.action === "absent"
|
|
306
|
+
? `[ok] ${where} was not there`
|
|
307
|
+
: row.action === "unchanged"
|
|
308
|
+
? `[ok] ${where} is up to date`
|
|
309
|
+
: `[ok] ${VERBS[row.action]} ${where}`;
|
|
310
|
+
out(`${line}\n`);
|
|
311
|
+
}
|
|
312
|
+
if (!remove && existsSync(join(home, ASTRYX_SKILL))) {
|
|
313
|
+
out(
|
|
314
|
+
`Note: ${displayPath(join(home, ASTRYX_SKILL), home)} is Astryx's own skill, which steers agents to \`astryx\` and @astryxdesign/*. The ui-common skill tells them to use ui-common instead in a project that depends on @lablup/ui-common. If you do not use Astryx directly, remove Astryx's with \`ui-common astryx init --user --remove-agents\`.\n`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return rows.some((r) => r.action === "kept" && !remove && r.path.endsWith("SKILL.md"))
|
|
318
|
+
? 1
|
|
319
|
+
: 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export const INIT_USER_HELP = `Usage: ui-common init --user [--remove-agents]
|
|
323
|
+
|
|
324
|
+
Install the ui-common agent skill for every agent on this computer:
|
|
325
|
+
~/.agents/skills/${USER_SKILL_NAME}/SKILL.md, linked into ~/.claude/skills and the
|
|
326
|
+
skill folders of the other agents Astryx reaches (Kiro, Windsurf, Letta,
|
|
327
|
+
Xcode, ~/.config/agents) when they are installed. It tells agents to use the
|
|
328
|
+
ui-common CLI (search, build, template, component, docs) and @lablup/ui-common
|
|
329
|
+
imports, not \`astryx\` and @astryxdesign/*, in a project that depends on
|
|
330
|
+
@lablup/ui-common. Run it again after upgrading @lablup/ui-common-cli.
|
|
331
|
+
|
|
332
|
+
This replaces what \`astryx init --user\` writes; \`ui-common astryx init --user\`
|
|
333
|
+
still runs Astryx's own.
|
|
334
|
+
|
|
335
|
+
--remove-agents Remove the skill, its links and its copies. A file you
|
|
336
|
+
edited, or one ui-common did not write, is kept.
|
|
337
|
+
|
|
338
|
+
Exit codes: 0 done, 1 the skill file was kept because it is not ui-common's
|
|
339
|
+
or was edited, 2 bad arguments.
|
|
340
|
+
`;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Astryx counterparts, and their props are reshaped.
|
|
4
4
|
*
|
|
5
5
|
* - Imports from the root barrel and from `@lablup/ui-common/components/<Name>`
|
|
6
|
-
* move to `@lablup/ui-common/<Astryx subpath
|
|
6
|
+
* move to `@lablup/ui-common/<Astryx subpath>`. Kept
|
|
7
7
|
* components (PageHeader, StatCard, the Skeleton composites, …) keep the
|
|
8
8
|
* import they had. `usePrefersReducedMotion` moves from `/hooks` to the root.
|
|
9
9
|
* - Re-exports (`export { Select } from …`) keep their public name.
|
|
@@ -689,18 +689,18 @@ export function Drawer(h) {
|
|
|
689
689
|
const value = attrValue(title);
|
|
690
690
|
replaceAttr(el, title, makeAttr(j, "label", attrExpression(j, title)));
|
|
691
691
|
if (value.kind === "expression" && !isStringish(value.expression)) {
|
|
692
|
-
h.todo("
|
|
692
|
+
h.todo("Drawer `label` must be a string; it was the title.");
|
|
693
693
|
}
|
|
694
694
|
}
|
|
695
695
|
if (title || getAttr(el, "subtitle") || getAttr(el, "footer")) {
|
|
696
696
|
todoAbout(
|
|
697
697
|
h,
|
|
698
698
|
"header",
|
|
699
|
-
"
|
|
699
|
+
"Drawer renders no header: compose DrawerHeader (title and close button) and the footer inside children.",
|
|
700
700
|
);
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
-
// 0.1's default width was "medium";
|
|
703
|
+
// 0.1's default width was "medium"; Astryx's Drawer default is narrower.
|
|
704
704
|
const widths = /** @type {Record<string, number>} */ (
|
|
705
705
|
h.removed.spec.valueMaps.width ?? {}
|
|
706
706
|
);
|
package/codemods/0.2/index.mjs
CHANGED
|
@@ -38,7 +38,7 @@ function notes(ctx) {
|
|
|
38
38
|
}
|
|
39
39
|
if (ctx.flags.packages.has(LAB_PACKAGE)) {
|
|
40
40
|
out.push(
|
|
41
|
-
`A
|
|
41
|
+
`A component moved to ${UIC}/lab: ${LAB_PACKAGE} is an optional peer of ui-common, pinned to the canary it is built against. The codemod added it to package.json and ${LAB_CSS} to the stylesheet entry it rewrote; import lab.css yourself if your entry is elsewhere.`,
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
out.push(
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* - in a library, drop React 18 from the react / react-dom peer ranges;
|
|
8
8
|
* - in a pnpm project, decline the Astryx postinstalls in `allowBuilds`;
|
|
9
9
|
* - add @astryxdesign/lab, pinned to the canary ui-common is built against,
|
|
10
|
-
* when a
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* when a moved component needs it (`requiresPackages` in the migration map;
|
|
11
|
+
* none since the Drawer moved to core in Astryx 0.7.2), and point its core
|
|
12
|
+
* peer at ui-common's core with the project's package manager's override
|
|
13
|
+
* (../../cli/lab-peer.mjs).
|
|
13
14
|
*/
|
|
14
15
|
import { applyLabOverride, CORE, detectPackageManager } from "../../cli/lab-peer.mjs";
|
|
15
16
|
import { targetUiCommonRoot, uiCommonPackageJson } from "../../cli/paths.mjs";
|
|
@@ -316,7 +317,8 @@ export function transformPackageJson(text, ctx) {
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
// Packages the map says a moved component needs (the
|
|
320
|
+
// Packages the map says a moved component needs (none since the Drawer
|
|
321
|
+
// moved to core in Astryx 0.7.2).
|
|
320
322
|
for (const [lab, range] of ctx.flags.packages) {
|
|
321
323
|
const added = ensure(lab, range);
|
|
322
324
|
if (added.length === 0) continue;
|
|
@@ -26,8 +26,8 @@ const { base, layerOrder, imports: replacement, entryFile } = STYLESHEETS;
|
|
|
26
26
|
const DROPPED = STYLESHEETS.dropped;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* The stylesheets that replace base.css. lab.css joins them when a
|
|
30
|
-
*
|
|
29
|
+
* The stylesheets that replace base.css. lab.css joins them when a moved
|
|
30
|
+
* component needs the lab package.
|
|
31
31
|
*
|
|
32
32
|
* @param {{flags?: {packages: Map<string, string>}}} ctx
|
|
33
33
|
* @returns {string[]}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.2.0-alpha.20: Astryx 0.7.2 moved `Drawer` and `DrawerHeader` from lab to
|
|
3
|
+
* core, so ui-common's `Drawer` fork moved from `@lablup/ui-common/lab` to
|
|
4
|
+
* `@lablup/ui-common/Drawer`, and the lab mirror no longer has either name.
|
|
5
|
+
*
|
|
6
|
+
* Imports and re-exports of those names from `@lablup/ui-common/lab` move to
|
|
7
|
+
* `@lablup/ui-common/Drawer` (joining an import from there when the file has
|
|
8
|
+
* one); the rest of a lab import stays. A namespace import of the lab mirror,
|
|
9
|
+
* or a module mock of it, is left as written and reported.
|
|
10
|
+
*
|
|
11
|
+
* Core's Drawer also took `isFullWidthOnMobile` into `experimental`, so the
|
|
12
|
+
* prop on a Drawer element becomes `experimental={{ isFullWidthOnMobile }}`;
|
|
13
|
+
* an element that already has `experimental` is left as written and reported.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const LAB = "@lablup/ui-common/lab";
|
|
17
|
+
export const DRAWER = "@lablup/ui-common/Drawer";
|
|
18
|
+
export const MOVED = new Set([
|
|
19
|
+
"Drawer",
|
|
20
|
+
"DrawerProps",
|
|
21
|
+
"DrawerHeader",
|
|
22
|
+
"DrawerHeaderProps",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** @param {any} specifier */
|
|
26
|
+
const importedName = (specifier) =>
|
|
27
|
+
specifier.imported?.name ?? specifier.imported?.value ?? specifier.local?.name;
|
|
28
|
+
|
|
29
|
+
/** @param {any} specifier */
|
|
30
|
+
const exportedFrom = (specifier) => specifier.local?.name ?? specifier.local?.value;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {{path: string, source: string}} file
|
|
34
|
+
* @param {{jscodeshift: any}} api
|
|
35
|
+
*/
|
|
36
|
+
export function moveDrawerImports(file, api) {
|
|
37
|
+
if (!file.source.includes(LAB) && !file.source.includes(FULL_WIDTH)) return undefined;
|
|
38
|
+
const j = api.jscodeshift;
|
|
39
|
+
const root = j(file.source);
|
|
40
|
+
let changed = false;
|
|
41
|
+
|
|
42
|
+
root
|
|
43
|
+
.find(j.ImportDeclaration, { source: { value: LAB } })
|
|
44
|
+
.forEach((/** @type {any} */ path) => {
|
|
45
|
+
const node = path.node;
|
|
46
|
+
const specifiers = node.specifiers ?? [];
|
|
47
|
+
const moved = specifiers.filter(
|
|
48
|
+
(/** @type {any} */ s) =>
|
|
49
|
+
s.type === "ImportSpecifier" && MOVED.has(importedName(s)),
|
|
50
|
+
);
|
|
51
|
+
if (moved.length === 0) return;
|
|
52
|
+
const kept = specifiers.filter((/** @type {any} */ s) => !moved.includes(s));
|
|
53
|
+
const target = root
|
|
54
|
+
.find(j.ImportDeclaration, {
|
|
55
|
+
source: { value: DRAWER },
|
|
56
|
+
importKind: node.importKind ?? "value",
|
|
57
|
+
})
|
|
58
|
+
.filter(
|
|
59
|
+
(/** @type {any} */ p) =>
|
|
60
|
+
!(p.node.specifiers ?? []).some(
|
|
61
|
+
(/** @type {any} */ s) => s.type !== "ImportSpecifier",
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
if (target.size() > 0) {
|
|
65
|
+
target.get().node.specifiers.push(...moved);
|
|
66
|
+
} else {
|
|
67
|
+
const declaration = j.importDeclaration(moved, j.stringLiteral(DRAWER));
|
|
68
|
+
if (node.importKind === "type") declaration.importKind = "type";
|
|
69
|
+
j(path).insertAfter(declaration);
|
|
70
|
+
}
|
|
71
|
+
if (kept.length > 0) node.specifiers = kept;
|
|
72
|
+
else j(path).remove();
|
|
73
|
+
changed = true;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
root
|
|
77
|
+
.find(j.ExportNamedDeclaration, { source: { value: LAB } })
|
|
78
|
+
.forEach((/** @type {any} */ path) => {
|
|
79
|
+
const node = path.node;
|
|
80
|
+
const moved = node.specifiers.filter((/** @type {any} */ s) =>
|
|
81
|
+
MOVED.has(exportedFrom(s)),
|
|
82
|
+
);
|
|
83
|
+
if (moved.length === 0) return;
|
|
84
|
+
const kept = node.specifiers.filter((/** @type {any} */ s) => !moved.includes(s));
|
|
85
|
+
const declaration = j.exportNamedDeclaration(
|
|
86
|
+
null,
|
|
87
|
+
moved,
|
|
88
|
+
j.stringLiteral(DRAWER),
|
|
89
|
+
);
|
|
90
|
+
if (node.exportKind === "type") declaration.exportKind = "type";
|
|
91
|
+
j(path).insertAfter(declaration);
|
|
92
|
+
if (kept.length > 0) node.specifiers = kept;
|
|
93
|
+
else j(path).remove();
|
|
94
|
+
changed = true;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (moveFullWidthOnMobile(j, root)) changed = true;
|
|
98
|
+
|
|
99
|
+
if (!changed) return undefined;
|
|
100
|
+
return root.toSource({ quote: /from\s+'/.test(file.source) ? "single" : "double" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const DRAWER_SOURCES = new Set([LAB, DRAWER, "@lablup/ui-common"]);
|
|
104
|
+
const FULL_WIDTH = "isFullWidthOnMobile";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rewrite `isFullWidthOnMobile` on elements of a Drawer imported from
|
|
108
|
+
* ui-common into core's `experimental={{ isFullWidthOnMobile }}`.
|
|
109
|
+
*
|
|
110
|
+
* @param {any} j
|
|
111
|
+
* @param {any} root
|
|
112
|
+
*/
|
|
113
|
+
function moveFullWidthOnMobile(j, root) {
|
|
114
|
+
/** @type {Set<string>} */
|
|
115
|
+
const locals = new Set();
|
|
116
|
+
root.find(j.ImportDeclaration).forEach((/** @type {any} */ path) => {
|
|
117
|
+
if (!DRAWER_SOURCES.has(path.node.source.value)) return;
|
|
118
|
+
for (const s of path.node.specifiers ?? []) {
|
|
119
|
+
if (s.type === "ImportSpecifier" && importedName(s) === "Drawer") {
|
|
120
|
+
locals.add(s.local.name);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (locals.size === 0) return false;
|
|
125
|
+
let changed = false;
|
|
126
|
+
root.find(j.JSXOpeningElement).forEach((/** @type {any} */ path) => {
|
|
127
|
+
const name = path.node.name;
|
|
128
|
+
if (name.type !== "JSXIdentifier" || !locals.has(name.name)) return;
|
|
129
|
+
const attrs = path.node.attributes ?? [];
|
|
130
|
+
const index = attrs.findIndex(
|
|
131
|
+
(/** @type {any} */ a) => a.type === "JSXAttribute" && a.name.name === FULL_WIDTH,
|
|
132
|
+
);
|
|
133
|
+
if (index < 0) return;
|
|
134
|
+
const hasExperimental = attrs.some(
|
|
135
|
+
(/** @type {any} */ a) =>
|
|
136
|
+
a.type === "JSXAttribute" && a.name.name === "experimental",
|
|
137
|
+
);
|
|
138
|
+
if (hasExperimental) return;
|
|
139
|
+
const value = attrs[index].value;
|
|
140
|
+
let expression;
|
|
141
|
+
if (value == null) expression = j.booleanLiteral(true);
|
|
142
|
+
else if (value.type === "JSXExpressionContainer") expression = value.expression;
|
|
143
|
+
else return;
|
|
144
|
+
const property = j.objectProperty(j.identifier(FULL_WIDTH), expression);
|
|
145
|
+
if (expression.type === "Identifier" && expression.name === FULL_WIDTH) {
|
|
146
|
+
property.shorthand = true;
|
|
147
|
+
}
|
|
148
|
+
attrs[index] = j.jsxAttribute(
|
|
149
|
+
j.jsxIdentifier("experimental"),
|
|
150
|
+
j.jsxExpressionContainer(j.objectExpression([property])),
|
|
151
|
+
);
|
|
152
|
+
changed = true;
|
|
153
|
+
});
|
|
154
|
+
return changed;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const CATEGORIES = {
|
|
158
|
+
"drawer-lab": {
|
|
159
|
+
title: "Drawer moved from @lablup/ui-common/lab to @lablup/ui-common/Drawer",
|
|
160
|
+
help: "Astryx 0.7.2 moved Drawer and DrawerHeader into core, so @lablup/ui-common/lab no longer exports them. The upgrade moved every named import; these uses go through the whole lab module (a namespace import or a module mock), so move their Drawer and DrawerHeader to @lablup/ui-common/Drawer by hand.",
|
|
161
|
+
},
|
|
162
|
+
"drawer-full-width": {
|
|
163
|
+
title: "Drawer's isFullWidthOnMobile is now experimental.isFullWidthOnMobile",
|
|
164
|
+
help: "Core's Drawer reads the option only from `experimental={{ isFullWidthOnMobile }}`; a top-level isFullWidthOnMobile is ignored and the drawer keeps the 56px page reveal on mobile. The upgrade rewrote the prop on Drawer elements it could see; move these by hand (a props object or spread, or an element that already has experimental).",
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const LAB_RE = /@lablup\/ui-common\/lab["']/;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Namespace imports and module mocks of the lab mirror, which the transform
|
|
172
|
+
* cannot prove free of Drawer, and any `isFullWidthOnMobile` it left outside
|
|
173
|
+
* `experimental`.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} file
|
|
176
|
+
* @param {string} source
|
|
177
|
+
*/
|
|
178
|
+
export function scanDrawer(file, source) {
|
|
179
|
+
/** @type {Array<{category: string, file: string, line: number, text: string}>} */
|
|
180
|
+
const out = [];
|
|
181
|
+
if (/@lablup\/ui-common/.test(source)) {
|
|
182
|
+
source.split("\n").forEach((line, i) => {
|
|
183
|
+
if (/\bisFullWidthOnMobile\b/.test(line) && !/\bexperimental\b/.test(line)) {
|
|
184
|
+
out.push({
|
|
185
|
+
category: "drawer-full-width",
|
|
186
|
+
file,
|
|
187
|
+
line: i + 1,
|
|
188
|
+
text: line.trim(),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (!LAB_RE.test(source)) return out;
|
|
194
|
+
source.split("\n").forEach((line, i) => {
|
|
195
|
+
if (!LAB_RE.test(line)) return;
|
|
196
|
+
const namespace = /import\s+(?:type\s+)?\*\s+as\s+\w+\s+from/.test(line);
|
|
197
|
+
const mock = /\b(?:vi|jest)\.(?:mock|doMock)\(/.test(line);
|
|
198
|
+
if (namespace || mock) {
|
|
199
|
+
out.push({ category: "drawer-lab", file, line: i + 1, text: line.trim() });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 0.2.0-alpha.20 step, ui-common's own part (Astryx's codemods for the
|
|
3
|
+
* same release are in ./upstream.json): the Drawer leaves the lab mirror.
|
|
4
|
+
*/
|
|
5
|
+
import { CATEGORIES, moveDrawerImports, scanDrawer } from "./drawer.mjs";
|
|
6
|
+
|
|
7
|
+
/** @type {import('../registry.mjs').Step} */
|
|
8
|
+
export default {
|
|
9
|
+
title: "0.2.0-alpha.20: Drawer moves from /lab to /Drawer",
|
|
10
|
+
transforms: [
|
|
11
|
+
{
|
|
12
|
+
id: "drawer-from-lab",
|
|
13
|
+
title: "Import Drawer and DrawerHeader from @lablup/ui-common/Drawer",
|
|
14
|
+
extensions: [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"],
|
|
15
|
+
parse: true,
|
|
16
|
+
run: moveDrawerImports,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
scan: scanDrawer,
|
|
20
|
+
categories: CATEGORIES,
|
|
21
|
+
notes: [
|
|
22
|
+
"Drawer and DrawerHeader are core components since Astryx 0.7.2: they come from @lablup/ui-common/Drawer (or the root) and no longer need @astryxdesign/lab or lab.css. A project that used lab only for the Drawer can drop both.",
|
|
23
|
+
],
|
|
24
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Written by `ui-common sync-astryx`. Astryx codemods consumers need when they upgrade across this ui-common version; `ui-common upgrade` runs them with @lablup/ui-common specifiers swapped for Astryx's, and moves the consumer's lab pin and core override.",
|
|
3
|
+
"astryx": {
|
|
4
|
+
"from": "0.7.0",
|
|
5
|
+
"to": "0.7.3"
|
|
6
|
+
},
|
|
7
|
+
"lab": {
|
|
8
|
+
"from": "0.7.0-canary.97a0d31",
|
|
9
|
+
"to": "0.7.3-canary.cac3d8d"
|
|
10
|
+
},
|
|
11
|
+
"codemods": [
|
|
12
|
+
{
|
|
13
|
+
"id": "migrate-list-edge-compensation-to-edge",
|
|
14
|
+
"version": "0.7.2",
|
|
15
|
+
"title": "Migrate List edgeCompensation to edge"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"optional": []
|
|
19
|
+
}
|
package/codemods/adopt/scan.mjs
CHANGED
|
@@ -40,7 +40,7 @@ export const CATEGORIES = {
|
|
|
40
40
|
},
|
|
41
41
|
escape: {
|
|
42
42
|
title: "Escape handlers of your own",
|
|
43
|
-
help: "`Modal`, the
|
|
43
|
+
help: "`Modal`, the `Drawer` and popovers close through one layer stack: one Escape closes the top layer only. A handler of yours that consumes Escape (inline edit, a search box) must call `event.preventDefault()`, or the stack closes the surrounding layer too.",
|
|
44
44
|
},
|
|
45
45
|
"agent-blocks": {
|
|
46
46
|
title: "Astryx agent blocks, and tools anchored on them",
|
package/codemods/registry.mjs
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* prerelease keys work: 0.1.0-alpha.19 < 0.2.0-alpha.0 < 0.2.0).
|
|
6
6
|
*
|
|
7
7
|
* Two kinds of step:
|
|
8
|
-
* - hand-written ones, listed in STATIC_STEPS (0.1 -> 0.2
|
|
8
|
+
* - hand-written ones, listed in STATIC_STEPS (0.1 -> 0.2; the Drawer's move
|
|
9
|
+
* off the lab mirror in 0.2.0-alpha.20);
|
|
9
10
|
* - `codemods/<version>/upstream.json`, written by `ui-common sync-astryx`
|
|
10
11
|
* when ui-common moves to a new Astryx: the Astryx codemods a consumer needs
|
|
11
12
|
* for that bump, run through ./upstream.mjs.
|
|
@@ -44,6 +45,10 @@ const STATIC_STEPS = [
|
|
|
44
45
|
version: "0.2.0-alpha.0",
|
|
45
46
|
load: async () => (await import("./0.2/index.mjs")).default,
|
|
46
47
|
},
|
|
48
|
+
{
|
|
49
|
+
version: "0.2.0-alpha.20",
|
|
50
|
+
load: async () => (await import("./0.2.0-alpha.20/index.mjs")).default,
|
|
51
|
+
},
|
|
47
52
|
];
|
|
48
53
|
|
|
49
54
|
/** Upstream steps recorded by `sync-astryx`: `codemods/<version>/upstream.json`. */
|
|
@@ -208,11 +208,10 @@
|
|
|
208
208
|
}
|
|
209
209
|
],
|
|
210
210
|
"replacement": {
|
|
211
|
-
"specifier": "@lablup/ui-common/
|
|
211
|
+
"specifier": "@lablup/ui-common/Drawer",
|
|
212
212
|
"names": { "Drawer": "Drawer", "DrawerProps": "DrawerProps" }
|
|
213
213
|
},
|
|
214
214
|
"alternatives": [],
|
|
215
|
-
"requiresPackages": { "@astryxdesign/lab": "0.7.0-canary.97a0d31" },
|
|
216
215
|
"propRenames": [
|
|
217
216
|
{
|
|
218
217
|
"from": "onClose",
|
|
@@ -224,7 +223,7 @@
|
|
|
224
223
|
],
|
|
225
224
|
"valueMaps": { "width": { "narrow": 400, "medium": 520, "wide": 900 } },
|
|
226
225
|
"manual": [
|
|
227
|
-
"
|
|
226
|
+
"Drawer renders no header: compose DrawerHeader (title and close button) and the footer inside children.",
|
|
228
227
|
"closeLabel, ariaLabelledBy and ariaDescribedBy have no counterpart.",
|
|
229
228
|
"preventDismiss and onDismissAttempt: decline the close in onOpenChange.",
|
|
230
229
|
"The .drawer classes are gone."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lablup/ui-common-cli",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.20",
|
|
4
4
|
"description": "The ui-common command line: the pinned Astryx CLI in @lablup/ui-common terms, the agent block and skill, the upgrade codemods, adopt (from Astryx) and doctor",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Lablup Inc.",
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@astryxdesign/cli": "0.7.
|
|
40
|
+
"@astryxdesign/cli": "0.7.3",
|
|
41
41
|
"jscodeshift": "^17.4.0",
|
|
42
42
|
"postcss": "^8.5.25",
|
|
43
43
|
"postcss-selector-parser": "^7.1.6"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@lablup/ui-common": "0.2.0-alpha.
|
|
46
|
+
"@lablup/ui-common": "0.2.0-alpha.20"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@lablup/ui-common": "0.2.0-alpha.
|
|
49
|
+
"@lablup/ui-common": "0.2.0-alpha.20"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-common
|
|
3
|
+
description: "@lablup/ui-common, Lablup's UI layer on the Astryx design system, is reachable on this computer through the ui-common command; use it when building or changing UI, choosing components or templates, layout, styling or theming in any project that depends on @lablup/ui-common, and use it there instead of the astryx skill."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
@lablup/ui-common is Lablup's UI layer on Astryx: Astryx's components under `@lablup/ui-common` import paths, the Lablup theme, and components of its own.
|
|
7
|
+
Use it when you build or change UI in a project whose package.json has `@lablup/ui-common`. There it replaces the `astryx` skill: run `ui-common`, not `astryx`, and never import `@astryxdesign/*`.
|
|
8
|
+
Run it through the project's package manager (`pnpm exec ui-common <cmd>`, `npx ui-common <cmd>`). Start with `ui-common search '<what you need>'`.
|
|
9
|
+
Then read `ui-common component <Name>` or `ui-common docs <topic>`, not the package's files. Import exactly what they print: the `@lablup/ui-common` root or one of its subpaths.
|
|
10
|
+
|
|
11
|
+
WORKFLOW — start every page from a template, never from scratch:
|
|
12
|
+
|
|
13
|
+
1. `ui-common build "<idea>"` — names the template to scaffold from and the parts it lacks.
|
|
14
|
+
2. `ui-common template <name> <path>` — scaffold it; keep its frame, gap and padding; replace the content.
|
|
15
|
+
3. `ui-common template <Block>` for parts it lacks; `ui-common component <Name>` to read props before using one.
|
|
16
|
+
|
|
17
|
+
Follow the project's own agent docs (the UI-COMMON block); `ui-common agents --write AGENTS.md` writes one where there is none.
|
|
18
|
+
Before you deliver, re-read each file; replace any raw <div>/<span>, imported .css/@apply, or hardcoded #hex/px with the component or a token. Then build the project and deliver that build, not a description of it.
|
|
19
|
+
If the project has no `ui-common` command, add `@lablup/ui-common-cli` as a devDependency at the version of its `@lablup/ui-common`.
|