create-dashral 0.0.0-stage → 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +44 -2
  2. package/index.mjs +185 -0
  3. package/package.json +31 -4
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # create-dashral
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
+ Start a new [Dashral](https://dashral.com) admin dashboard (Next.js, React, TypeScript, Tailwind CSS) in one command:
4
+
5
+ ```bash
6
+ npx create-dashral my-app
7
+ cd my-app
8
+ pnpm dev
9
+ ```
10
+
11
+ It asks which template to keep (Classic, Inset or both), demo pages or a blank page, the languages, your app's name
12
+ and its look, then makes a Next.js project laid out like a new Next.js app, with its own Git history:
13
+
14
+ ```
15
+ my-app/
16
+ ├── app/ pages
17
+ ├── components/ the app's own components
18
+ ├── lib/ messages/ public/
19
+ ├── package.json "@dashral/ui" from npm
20
+ └── next.config.ts tsconfig.json
21
+ ```
22
+
23
+ The UI components come from [`@dashral/ui`](https://www.npmjs.com/package/@dashral/ui)
24
+ (`import { Button } from "@dashral/ui/components/button"`): `pnpm up @dashral/ui` brings in new ones and fixes. You need
25
+ **Node.js 20+**, **Git** and **pnpm** (`corepack enable pnpm`).
26
+
27
+ Every answer is also a flag, so it can run without questions:
28
+
29
+ ```bash
30
+ npx create-dashral my-app --template inset --pages blank --languages en --name "Acme" --primary indigo
31
+ ```
32
+
33
+ The theme customizer's **Copy setup command** in the [live demo](https://demo.dashral.com) writes these flags for you.
34
+
35
+ | Option | |
36
+ | --- | --- |
37
+ | `--monorepo` | Keep Dashral's whole repository (`apps/`, `packages/`) and history instead, so `pnpm dashral update` can bring in later releases, pages too ([Getting updates](https://github.com/hasralgana/dashral#getting-updates)) |
38
+ | `--pro` | Start from Dashral Pro (your GitHub account needs access to it; monorepo for now) |
39
+ | `--release v1.2.0` | An earlier release instead of the newest |
40
+ | `--from <url>` | Clone another repository, e.g. a fork, or Pro over SSH |
41
+ | `--no-install` | Skip `pnpm install` |
42
+
43
+ Any other option goes to `pnpm dashral init`; `npx create-dashral --help` lists them.
44
+
45
+ MIT licensed.
package/index.mjs ADDED
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ // `npx create-dashral my-app`: a new dashboard from Dashral's newest release, in one command. By default it makes a
3
+ // Next.js project of its own, like `create-next-app`: the release is cloned, `pnpm dashral init --layout flat` keeps
4
+ // only the app (the components come from npm as @dashral/ui), the project starts a Git history of its own and its
5
+ // dependencies are installed. With --monorepo it keeps the repository's layout and history instead, for
6
+ // `pnpm dashral update`. Nothing about the setup lives here: the release's scripts/dashral.mjs does it, so this file
7
+ // rarely needs a new version.
8
+ //
9
+ // npx create-dashral my-app
10
+ // npx create-dashral my-app --template inset --pages blank --languages en --name "Acme" --primary indigo
11
+ // npx create-dashral my-app --monorepo (the whole repository, for pnpm dashral update)
12
+ // npx create-dashral my-app --pro --apps crm,kanban --name "Acme" (Dashral Pro: needs your GitHub access)
13
+
14
+ import { spawnSync } from "node:child_process"
15
+ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs"
16
+ import { join, resolve } from "node:path"
17
+ import { argv, exit, platform, versions } from "node:process"
18
+
19
+ const repos = {
20
+ free: "https://github.com/hasralgana/dashral.git",
21
+ pro: "https://github.com/hasralgana/dashral-pro.git",
22
+ }
23
+
24
+ const help = `Usage: npx create-dashral <folder> [options]
25
+
26
+ Starts a new Next.js dashboard in <folder> from Dashral's newest release, as a project of its own (the components
27
+ come from npm as @dashral/ui), and runs \`pnpm dashral init\`, which asks a few questions (or reads the flags below).
28
+
29
+ Options of create-dashral:
30
+ --monorepo Keep the whole repository (apps/, packages/) and its history, to bring in later releases
31
+ with \`pnpm dashral update\`
32
+ --pro Start from Dashral Pro (your GitHub account needs access to the repository; monorepo for now)
33
+ --release v1.2.0 An earlier release instead of the newest
34
+ --from <url> Clone another repository (a fork, or Pro over SSH: git@github.com:hasralgana/dashral-pro.git)
35
+ --no-install Set up only; run \`pnpm install\` yourself
36
+
37
+ Every other option goes to \`pnpm dashral init\`:
38
+ --template classic|inset|both --pages demo|blank --languages all|en --customizer keep|remove
39
+ --name "Your App" --primary --light --dark --card --radius --font --yes
40
+ Pro: --apps all|crm,kanban,… --samples keep|remove
41
+
42
+ The theme customizer's "Copy setup command" at https://demo.dashral.com writes these flags for you.
43
+ Docs: https://dashral.com/docs/installation`
44
+
45
+ // ── Arguments ────────────────────────────────────────────────────────────────────────────────────────────────────
46
+
47
+ /** This script's own flags, and the rest passed to `dashral init` as they were typed. */
48
+ function parseArgs(args) {
49
+ const own = { pro: false, monorepo: false, install: true, release: null, from: null, help: false }
50
+ const rest = []
51
+ let folder = null
52
+ for (let i = 0; i < args.length; i++) {
53
+ const arg = args[i]
54
+ const [key, inline] = arg.startsWith("--") ? arg.slice(2).split(/=(.*)/s) : [null]
55
+ const value = () => (inline !== undefined ? inline : args[++i])
56
+ if (arg === "-h" || key === "help") own.help = true
57
+ else if (key === "pro") own.pro = true
58
+ else if (key === "monorepo") own.monorepo = true
59
+ else if (key === "no-install") own.install = false
60
+ else if (key === "release" || key === "from") {
61
+ own[key] = value()
62
+ if (!own[key] || own[key].startsWith("--")) fail(`--${key} needs a value.`)
63
+ } else if (key === "layout") fail("Use --monorepo for the repository's layout; the project is flat without it.")
64
+ else if (key === null && folder === null) folder = arg
65
+ else rest.push(arg)
66
+ }
67
+ return { folder, own, rest }
68
+ }
69
+
70
+ // ── Helpers ──────────────────────────────────────────────────────────────────────────────────────────────────────
71
+
72
+ function fail(message) {
73
+ console.error(`\n✖ ${message}`)
74
+ exit(1)
75
+ }
76
+
77
+ /** Runs a command in the terminal (its output and questions shown as they come); its exit code, or null. */
78
+ function run(command, args, cwd, quiet = false) {
79
+ // Windows runs pnpm through a .cmd shim, which needs a shell. DASHRAL_CREATE: init leaves the next steps to us.
80
+ const env = { ...process.env, DASHRAL_CREATE: "1" }
81
+ const result = spawnSync(command, args, { cwd, env, stdio: quiet ? "ignore" : "inherit", shell: platform === "win32" })
82
+ return result.error ? null : result.status
83
+ }
84
+
85
+ /** A command's output, or null when it can't run. */
86
+ function output(command, args) {
87
+ const result = spawnSync(command, args, { encoding: "utf8", shell: platform === "win32" })
88
+ return result.status === 0 ? result.stdout.trim() : null
89
+ }
90
+
91
+ const versionOf = (tag) => tag.slice(1).split(".").map(Number)
92
+ const newer = (a, b) => {
93
+ const [x, y] = [versionOf(a), versionOf(b)]
94
+ for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i]
95
+ return 0
96
+ }
97
+
98
+ // ── Main ─────────────────────────────────────────────────────────────────────────────────────────────────────────
99
+
100
+ const { folder, own, rest } = parseArgs(argv.slice(2))
101
+ if (own.help || !folder) {
102
+ console.log(help)
103
+ exit(own.help ? 0 : 1)
104
+ }
105
+
106
+ if (Number(versions.node.split(".")[0]) < 20) fail(`Dashral needs Node.js 20 or newer (this is ${versions.node}).`)
107
+ if (output("git", ["--version"]) === null) fail("Git wasn't found. Install it from https://git-scm.com and run this again.")
108
+ if (own.install && output("pnpm", ["--version"]) === null) {
109
+ fail("pnpm wasn't found. Turn it on with `corepack enable pnpm` (or `npm install -g pnpm`) and run this again.")
110
+ }
111
+
112
+ const target = resolve(folder)
113
+ if (existsSync(target) && readdirSync(target).length) fail(`${folder} isn't empty. Pick a new folder.`)
114
+
115
+ const url = own.from ?? (own.pro ? repos.pro : repos.free)
116
+ const access = own.pro ? " Check that your GitHub account has access to Dashral Pro (accept the invitation GitHub emailed you)." : ""
117
+
118
+ // The release: the newest version tag, or the one --release names.
119
+ console.log(`Looking for the releases of ${url}…`)
120
+ const listed = output("git", ["ls-remote", "--tags", "--refs", url, "v*"])
121
+ if (listed === null) fail(`Couldn't reach ${url}.${access}`)
122
+ const tags = listed
123
+ .split("\n")
124
+ .map((line) => line.split("refs/tags/")[1])
125
+ .filter((tag) => /^v\d+\.\d+\.\d+$/.test(tag ?? ""))
126
+ .sort(newer)
127
+ let release = tags.at(-1)
128
+ if (own.release) {
129
+ release = own.release.startsWith("v") ? own.release : `v${own.release}`
130
+ if (!tags.includes(release)) fail(`There's no release ${release}. The releases: ${tags.join(", ") || "none yet"}.`)
131
+ }
132
+ if (!release) fail(`${url} has no releases yet.`)
133
+
134
+ let flat = !own.monorepo
135
+ if (flat && own.pro) {
136
+ console.log(" Dashral Pro starts as the monorepo for now (a project of its own is coming).")
137
+ flat = false
138
+ }
139
+
140
+ // Flat: the files are enough. The monorepo keeps the whole history: `pnpm dashral update` merges later releases into it.
141
+ console.log(`\nCloning Dashral ${release} into ${folder}…`)
142
+ const depth = flat ? ["--depth", "1"] : []
143
+ if (run("git", ["-c", "advice.detachedHead=false", "clone", "--quiet", ...depth, "--branch", release, url, target]) !== 0) fail(`Couldn't clone ${url}.${access}`)
144
+ if (flat && !readFileSync(join(target, "scripts/dashral.mjs"), "utf8").includes("--layout")) {
145
+ // A release from before the flat layout: set it up as the monorepo.
146
+ console.log(` ${release} predates the flat layout, so it's set up as the monorepo.`)
147
+ flat = false
148
+ rmSync(target, { recursive: true, force: true })
149
+ if (run("git", ["-c", "advice.detachedHead=false", "clone", "--quiet", "--branch", release, url, target]) !== 0) fail(`Couldn't clone ${url}.${access}`)
150
+ }
151
+ const restart = `It's a fresh copy, so you can delete ${folder} and start again.`
152
+
153
+ if (flat) {
154
+ // Setting up needs no dependencies: install once, in the project as it ends up.
155
+ console.log("")
156
+ if (run("node", ["scripts/dashral.mjs", "init", "--layout", "flat", ...rest], target) !== 0) fail(`The setup stopped. ${restart}`)
157
+ // The project's own history, from its first commit.
158
+ rmSync(join(target, ".git"), { recursive: true, force: true })
159
+ run("git", ["init", "--quiet", "-b", "main"], target)
160
+ if (own.install) {
161
+ console.log("\nInstalling the dependencies…")
162
+ if (run("pnpm", ["install"], target) !== 0) fail(`pnpm install stopped. Fix what it says, then run \`pnpm install\` in ${folder}.`)
163
+ }
164
+ run("git", ["add", "-A"], target, true)
165
+ const committed = run("git", ["commit", "--quiet", "-m", "Initial commit from create-dashral"], target, true) === 0
166
+ console.log(`
167
+ Your dashboard is in ${folder}:
168
+ cd ${folder}${own.install ? "" : "\n pnpm install"}
169
+ pnpm dev${committed ? "" : "\n\n(Git has no name and email here yet, so nothing was committed: commit when you're ready.)"}`)
170
+ } else {
171
+ // A tag checks out without a branch: start yours, so your commits have one.
172
+ if (run("git", ["switch", "--quiet", "-c", "main"], target) !== 0) fail("Couldn't start the main branch.")
173
+ if (own.install) {
174
+ console.log("\nInstalling the dependencies…")
175
+ if (run("pnpm", ["install"], target) !== 0) fail(`pnpm install stopped. Fix what it says, then in ${folder}: pnpm install && pnpm dashral init`)
176
+ }
177
+ console.log("")
178
+ if (run("node", ["scripts/dashral.mjs", "init", ...rest], target) !== 0) {
179
+ fail(`The setup stopped. ${restart} Or fix what it says and run \`pnpm dashral init\` in it.`)
180
+ }
181
+ console.log(`
182
+ Your dashboard is in ${folder}:
183
+ cd ${folder}${own.install ? "" : "\n pnpm install"}
184
+ pnpm dev`)
185
+ }
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "create-dashral",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Start a new Dashral admin dashboard (Next.js, React, Tailwind CSS) in one command.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-dashral": "index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "homepage": "https://dashral.com/docs/installation",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/hasralgana/dashral.git",
20
+ "directory": "packages/create-dashral"
21
+ },
22
+ "bugs": "https://github.com/hasralgana/dashral/issues",
23
+ "keywords": [
24
+ "dashral",
25
+ "dashboard",
26
+ "admin",
27
+ "template",
28
+ "nextjs",
29
+ "react",
30
+ "tailwindcss",
31
+ "shadcn"
32
+ ]
33
+ }