@azmxsa/cli 0.0.0-stage → 1.2.2

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 CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # azmx
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The AZM X Design System installer. It adds components, blocks and tokens from the private AZM X registry to a React and Tailwind project, pinned to one released version. Nothing to install: run it with npx.
4
+
5
+ ```bash
6
+ gh auth login # or set GITHUB_TOKEN to a token that can read the design system repository
7
+ npx @azmxsa/cli init # adds the AZM X registry to components.json
8
+ npx @azmxsa/cli add tokens
9
+ npx @azmxsa/cli add button dialog form-field
10
+ npx @azmxsa/cli list
11
+ ```
12
+
13
+ - `npx @azmxsa/cli add <name...> [flags]`: flags such as `-o` (overwrite) and `-y` pass through to shadcn.
14
+ - `--ref <tag|main>`: install another version than this installer's own (`azmx@1.2.0` installs `v1.2.0`).
15
+ - The components themselves stay private; only this installer is public.
package/bin/azmx.mjs ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs"
3
+ import { execFileSync, spawnSync } from "node:child_process"
4
+ import { HELP, REPO, VERSION, defaultRef, childEnv, isValidRef, itemsFor, parseArgs, refFromUrl, shadcnArgs, tokenLeaks, unsafeFlags, withRegistry } from "../lib/cli.mjs"
5
+
6
+ const CONFIG = "components.json"
7
+ const npx = process.platform === "win32" ? "npx.cmd" : "npx"
8
+ const fail = (message) => {
9
+ console.error(`azmx: ${message}`)
10
+ process.exit(1)
11
+ }
12
+
13
+ // The registry is private: the GitHub token comes from the environment, or from the GitHub CLI.
14
+ function token() {
15
+ if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN
16
+ if (process.env.GH_TOKEN) return process.env.GH_TOKEN
17
+ try {
18
+ return execFileSync("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || undefined
19
+ } catch {
20
+ return undefined
21
+ }
22
+ }
23
+
24
+ const readConfig = () => (existsSync(CONFIG) ? JSON.parse(readFileSync(CONFIG, "utf8")) : undefined)
25
+ const currentRef = (config) => refFromUrl(config?.registries?.["@azmx"]?.url)
26
+
27
+ function init(ref) {
28
+ if (!isValidRef(ref)) fail(`"${ref}" is not a version tag or branch name.`)
29
+ let config = readConfig()
30
+ if (!config) {
31
+ console.log("No components.json yet: setting up the project first.")
32
+ const run = spawnSync(npx, shadcnArgs("init", [], []), { stdio: "inherit" })
33
+ if (run.status !== 0) process.exit(run.status ?? 1)
34
+ config = readConfig()
35
+ if (!config) fail("components.json was not created.")
36
+ }
37
+ writeFileSync(CONFIG, JSON.stringify(withRegistry(config, ref), null, 2) + "\n")
38
+ console.log(`AZM X registry set to ${ref} in ${CONFIG}.`)
39
+ }
40
+
41
+ function run(command, names, ref, pass) {
42
+ const flags = unsafeFlags(pass)
43
+ if (flags.length) fail(`run azmx inside the project folder instead of passing ${flags.join(", ")}.`)
44
+ // Anything but AZM X's exact address is rewritten, so the token only ever goes to GitHub.
45
+ if (ref || !currentRef(readConfig())) init(ref ?? defaultRef())
46
+ const leaks = tokenLeaks(readConfig())
47
+ if (leaks.length) fail(`components.json reads secrets for a registry outside https://raw.githubusercontent.com (${leaks.join(", ")}). Remove those placeholders, then try again.`)
48
+ const auth = token()
49
+ if (!auth) fail(`set GITHUB_TOKEN to a token that can read ${REPO}, or sign in with: gh auth login`)
50
+ const result = spawnSync(npx, shadcnArgs(command, itemsFor(names), pass), { stdio: "inherit", env: childEnv(process.env, auth) })
51
+ process.exit(result.status ?? 1)
52
+ }
53
+
54
+ async function list(ref) {
55
+ const auth = token()
56
+ if (!auth) fail(`set GITHUB_TOKEN to a token that can read ${REPO}, or sign in with: gh auth login`)
57
+ const at = ref ?? currentRef(readConfig()) ?? defaultRef()
58
+ if (!isValidRef(at)) fail(`"${at}" is not a version tag or branch name.`)
59
+ const res = await fetch(`https://raw.githubusercontent.com/${REPO}/${at}/registry.json`, { headers: { Authorization: `Bearer ${auth}` } })
60
+ if (!res.ok) fail(`could not read the registry at ${at} (${res.status}).`)
61
+ const { items } = await res.json()
62
+ const groups = { "registry:style": "Tokens", "registry:lib": "Libraries", "registry:ui": "Components", "registry:block": "Blocks" }
63
+ for (const [type, title] of Object.entries(groups)) {
64
+ const rows = items.filter((i) => i.type === type && !i.meta?.deprecated)
65
+ if (!rows.length) continue
66
+ console.log(`\n${title}`)
67
+ for (const i of rows) console.log(` ${i.name.padEnd(22)} ${i.title ?? ""}`)
68
+ }
69
+ console.log(`\nAZM X Design System ${at}. Add one with: azmx add <name>`)
70
+ }
71
+
72
+ const { command, names, ref, pass } = parseArgs(process.argv.slice(2))
73
+ if (command === "version") console.log(VERSION)
74
+ else if (command === "help") console.log(HELP)
75
+ else if (command === "init") init(ref ?? defaultRef())
76
+ else if (command === "add") names.length ? run("add", names, ref, pass) : fail("name what to add, e.g. azmx add button")
77
+ else if (command === "view") names.length ? run("view", names, ref, pass) : fail("name what to view, e.g. azmx view button")
78
+ else if (command === "list") await list(ref)
79
+ else fail(`unknown command "${command}". Run azmx --help.`)
package/lib/cli.mjs ADDED
@@ -0,0 +1,97 @@
1
+ // The AZM X installer. It writes the private registry into components.json and runs the shadcn
2
+ // CLI the system is built with, so a product types `azmx add button` and never shadcn's name.
3
+
4
+ /** The installer's own version, the same as the system's (tests keep it equal to package.json). */
5
+ export const VERSION = "1.2.2"
6
+ /** The shadcn CLI the system's registry is built and tested with. */
7
+ export const SHADCN_VERSION = "4.22.1"
8
+ export const REPO = "Gamaleldientarek/azmx-design-system"
9
+
10
+ /** The tag of the system this installer belongs to: azmx 1.1.0 installs v1.1.0. */
11
+ export const defaultRef = () => `v${VERSION}`
12
+
13
+ export function parseArgs(argv) {
14
+ const args = [...argv]
15
+ let command = args.shift() ?? "help"
16
+ if (command === "--version" || command === "-v") command = "version"
17
+ if (command === "--help" || command === "-h") command = "help"
18
+ const names = []
19
+ const pass = []
20
+ let ref
21
+ for (let i = 0; i < args.length; i++) {
22
+ const a = args[i]
23
+ if (a === "--ref") ref = args[++i]
24
+ else if (a.startsWith("--ref=")) ref = a.slice(6)
25
+ else if (a.startsWith("-")) pass.push(a)
26
+ else names.push(a)
27
+ }
28
+ return { command, names, ref, pass }
29
+ }
30
+
31
+ export function registryEntry(ref) {
32
+ return {
33
+ url: `https://raw.githubusercontent.com/${REPO}/${ref}/public/r/{name}.json`,
34
+ headers: { Authorization: "Bearer ${GITHUB_TOKEN}" },
35
+ }
36
+ }
37
+
38
+ /** components.json with the AZM X registry added (or moved to `ref`), everything else kept. */
39
+ export function withRegistry(config, ref) {
40
+ return { ...config, registries: { ...(config.registries ?? {}), "@azmx": registryEntry(ref) } }
41
+ }
42
+
43
+ const REF = /^[A-Za-z0-9._-]+$/
44
+ const REGISTRY_URL = /^https:\/\/raw\.githubusercontent\.com\/Gamaleldientarek\/azmx-design-system\/([A-Za-z0-9._-]+)\/public\/r\/\{name\}\.json$/
45
+
46
+ /** A tag or branch name, nothing that could change the address's path. */
47
+ export const isValidRef = (ref) => typeof ref === "string" && REF.test(ref) && !ref.startsWith(".")
48
+
49
+ /** The version in an @azmx registry address, only when the address is exactly AZM X's own. */
50
+ export const refFromUrl = (url) => (typeof url === "string" ? url.match(REGISTRY_URL)?.[1] : undefined)
51
+
52
+ /** Registries in components.json that read anything from the environment (a ${...} placeholder
53
+ * anywhere in the entry) but don't point at https://raw.githubusercontent.com without credentials. */
54
+ export function tokenLeaks(config) {
55
+ return Object.entries(config?.registries ?? {})
56
+ .filter(([, entry]) => {
57
+ if (!JSON.stringify(entry ?? {}).includes("${")) return false
58
+ try {
59
+ const u = new URL(String(typeof entry === "string" ? entry : entry?.url).replace("{name}", "x"))
60
+ return !(u.protocol === "https:" && u.host === "raw.githubusercontent.com" && !u.username && !u.password)
61
+ } catch {
62
+ return true
63
+ }
64
+ })
65
+ .map(([name]) => name)
66
+ }
67
+
68
+ /** shadcn flags that would point it at another folder's components.json, which azmx hasn't checked. */
69
+ export const unsafeFlags = (pass) => pass.filter((f) => f === "--cwd" || f === "-c" || f.startsWith("--cwd=") || /^-c./.test(f))
70
+
71
+ /** The environment shadcn runs with: no GitHub tokens but the one azmx checked, as GITHUB_TOKEN. */
72
+ export function childEnv(env, token) {
73
+ const out = {}
74
+ for (const [k, v] of Object.entries(env)) if (!/^(GH_|GITHUB_)/.test(k) || !/TOKEN/.test(k)) out[k] = v
75
+ return { ...out, GITHUB_TOKEN: token }
76
+ }
77
+
78
+ export const itemsFor = (names) => names.map((n) => (n.startsWith("@azmx/") ? n : `@azmx/${n}`))
79
+
80
+ /** Arguments for npx: the pinned shadcn CLI, the command, the items and the flags passed on. */
81
+ export const shadcnArgs = (command, items, pass) => ["--yes", `shadcn@${SHADCN_VERSION}`, command, ...items, ...pass]
82
+
83
+ export const HELP = `azmx ${VERSION}: the AZM X Design System installer
84
+
85
+ Usage
86
+ npx @azmxsa/cli init [--ref v${VERSION}] add the AZM X registry to components.json
87
+ npx @azmxsa/cli add <name...> [flags] add components, blocks or tokens (flags go to shadcn, e.g. -o)
88
+ npx @azmxsa/cli list list everything the registry has
89
+ npx @azmxsa/cli --version
90
+
91
+ The registry is private: set GITHUB_TOKEN to a token that can read ${REPO},
92
+ or sign in with the GitHub CLI (gh auth login) and azmx uses its token.
93
+
94
+ Examples
95
+ npx @azmxsa/cli add tokens
96
+ npx @azmxsa/cli add button dialog form-field
97
+ npx @azmxsa/cli add hero --ref main`
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@azmxsa/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.2.2",
4
+ "description": "Add AZM X Design System components, blocks and tokens to a React and Tailwind project.",
5
+ "type": "module",
6
+ "bin": {
7
+ "azmx": "bin/azmx.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.17"
16
+ },
17
+ "license": "UNLICENSED",
18
+ "private": false,
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }