create-dashral 0.1.0 → 0.2.1
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 +17 -7
- package/index.mjs +100 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,21 @@ cd my-app
|
|
|
8
8
|
pnpm dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Leave out the folder and it asks for the project's name first (Enter takes `my-app`). Then 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`).
|
|
14
26
|
|
|
15
27
|
Every answer is also a flag, so it can run without questions:
|
|
16
28
|
|
|
@@ -22,14 +34,12 @@ The theme customizer's **Copy setup command** in the [live demo](https://demo.da
|
|
|
22
34
|
|
|
23
35
|
| Option | |
|
|
24
36
|
| --- | --- |
|
|
25
|
-
| `--
|
|
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) |
|
|
26
39
|
| `--release v1.2.0` | An earlier release instead of the newest |
|
|
27
40
|
| `--from <url>` | Clone another repository, e.g. a fork, or Pro over SSH |
|
|
28
41
|
| `--no-install` | Skip `pnpm install` |
|
|
29
42
|
|
|
30
43
|
Any other option goes to `pnpm dashral init`; `npx create-dashral --help` lists them.
|
|
31
44
|
|
|
32
|
-
The project keeps the release's Git history, so later releases come in with `pnpm dashral update`. See
|
|
33
|
-
[Getting updates](https://github.com/hasralgana/dashral#getting-updates).
|
|
34
|
-
|
|
35
45
|
MIT licensed.
|
package/index.mjs
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `npx create-dashral my-app`: a new dashboard from Dashral's newest release, in one command.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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.
|
|
6
8
|
//
|
|
7
9
|
// npx create-dashral my-app
|
|
8
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)
|
|
9
12
|
// npx create-dashral my-app --pro --apps crm,kanban --name "Acme" (Dashral Pro: needs your GitHub access)
|
|
10
13
|
|
|
11
14
|
import { spawnSync } from "node:child_process"
|
|
12
|
-
import { existsSync, readdirSync } from "node:fs"
|
|
13
|
-
import { resolve } from "node:path"
|
|
14
|
-
import { argv, exit, platform, versions } from "node:process"
|
|
15
|
+
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs"
|
|
16
|
+
import { join, resolve } from "node:path"
|
|
17
|
+
import { argv, exit, platform, stdin, stdout, versions } from "node:process"
|
|
18
|
+
import { createInterface } from "node:readline/promises"
|
|
15
19
|
|
|
16
20
|
const repos = {
|
|
17
21
|
free: "https://github.com/hasralgana/dashral.git",
|
|
18
22
|
pro: "https://github.com/hasralgana/dashral-pro.git",
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
const help = `Usage: npx create-dashral
|
|
25
|
+
const help = `Usage: npx create-dashral [folder] [options]
|
|
22
26
|
|
|
23
|
-
Starts a new dashboard in
|
|
24
|
-
\`pnpm dashral init\`, which asks a few questions (or reads
|
|
27
|
+
Starts a new Next.js dashboard in [folder] (asked for when left out) from Dashral's newest release, as a project of its own (the components
|
|
28
|
+
come from npm as @dashral/ui), and runs \`pnpm dashral init\`, which asks a few questions (or reads the flags below).
|
|
25
29
|
|
|
26
30
|
Options of create-dashral:
|
|
27
|
-
--
|
|
31
|
+
--monorepo Keep the whole repository (apps/, packages/) and its history, to bring in later releases
|
|
32
|
+
with \`pnpm dashral update\`
|
|
33
|
+
--pro Start from Dashral Pro (your GitHub account needs access to the repository; monorepo for now)
|
|
28
34
|
--release v1.2.0 An earlier release instead of the newest
|
|
29
35
|
--from <url> Clone another repository (a fork, or Pro over SSH: git@github.com:hasralgana/dashral-pro.git)
|
|
30
|
-
--no-install
|
|
36
|
+
--no-install Set up only; run \`pnpm install\` yourself
|
|
31
37
|
|
|
32
38
|
Every other option goes to \`pnpm dashral init\`:
|
|
33
39
|
--template classic|inset|both --pages demo|blank --languages all|en --customizer keep|remove
|
|
@@ -39,9 +45,13 @@ Docs: https://dashral.com/docs/installation`
|
|
|
39
45
|
|
|
40
46
|
// ── Arguments ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
41
47
|
|
|
48
|
+
/** Flags that take no value: a word after one of these can be the folder; after any other flag, it's that flag's value. */
|
|
49
|
+
const switches = new Set(["help", "pro", "monorepo", "no-install", "yes", "force"])
|
|
50
|
+
|
|
42
51
|
/** This script's own flags, and the rest passed to `dashral init` as they were typed. */
|
|
43
52
|
function parseArgs(args) {
|
|
44
|
-
const
|
|
53
|
+
const takesValue = (previous) => Boolean(previous?.startsWith("--") && !previous.includes("=") && !switches.has(previous.slice(2)))
|
|
54
|
+
const own = { pro: false, monorepo: false, install: true, release: null, from: null, help: false }
|
|
45
55
|
const rest = []
|
|
46
56
|
let folder = null
|
|
47
57
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -50,11 +60,13 @@ function parseArgs(args) {
|
|
|
50
60
|
const value = () => (inline !== undefined ? inline : args[++i])
|
|
51
61
|
if (arg === "-h" || key === "help") own.help = true
|
|
52
62
|
else if (key === "pro") own.pro = true
|
|
63
|
+
else if (key === "monorepo") own.monorepo = true
|
|
53
64
|
else if (key === "no-install") own.install = false
|
|
54
65
|
else if (key === "release" || key === "from") {
|
|
55
66
|
own[key] = value()
|
|
56
67
|
if (!own[key] || own[key].startsWith("--")) fail(`--${key} needs a value.`)
|
|
57
|
-
} else if (key ===
|
|
68
|
+
} else if (key === "layout") fail("Use --monorepo for the repository's layout; the project is flat without it.")
|
|
69
|
+
else if (key === null && folder === null && !takesValue(args[i - 1])) folder = arg
|
|
58
70
|
else rest.push(arg)
|
|
59
71
|
}
|
|
60
72
|
return { folder, own, rest }
|
|
@@ -68,9 +80,10 @@ function fail(message) {
|
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
/** Runs a command in the terminal (its output and questions shown as they come); its exit code, or null. */
|
|
71
|
-
function run(command, args, cwd) {
|
|
72
|
-
// Windows runs pnpm through a .cmd shim, which needs a shell.
|
|
73
|
-
const
|
|
83
|
+
function run(command, args, cwd, quiet = false) {
|
|
84
|
+
// Windows runs pnpm through a .cmd shim, which needs a shell. DASHRAL_CREATE: init leaves the next steps to us.
|
|
85
|
+
const env = { ...process.env, DASHRAL_CREATE: "1" }
|
|
86
|
+
const result = spawnSync(command, args, { cwd, env, stdio: quiet ? "ignore" : "inherit", shell: platform === "win32" })
|
|
74
87
|
return result.error ? null : result.status
|
|
75
88
|
}
|
|
76
89
|
|
|
@@ -89,10 +102,31 @@ const newer = (a, b) => {
|
|
|
89
102
|
|
|
90
103
|
// ── Main ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
91
104
|
|
|
92
|
-
const
|
|
93
|
-
|
|
105
|
+
const parsed = parseArgs(argv.slice(2))
|
|
106
|
+
const { own, rest } = parsed
|
|
107
|
+
let folder = parsed.folder
|
|
108
|
+
if (own.help) {
|
|
94
109
|
console.log(help)
|
|
95
|
-
exit(
|
|
110
|
+
exit(0)
|
|
111
|
+
}
|
|
112
|
+
// No folder given: ask for one in a terminal, as create-next-app does (Enter takes my-app); elsewhere, say how.
|
|
113
|
+
if (!folder) {
|
|
114
|
+
if (!stdin.isTTY) {
|
|
115
|
+
console.log(help)
|
|
116
|
+
exit(1)
|
|
117
|
+
}
|
|
118
|
+
const rl = createInterface({ input: stdin, output: stdout })
|
|
119
|
+
for (;;) {
|
|
120
|
+
const answer = (await rl.question("What is your project named? (my-app) ")).trim() || "my-app"
|
|
121
|
+
const path = resolve(answer)
|
|
122
|
+
if (existsSync(path) && readdirSync(path).length) {
|
|
123
|
+
console.log(` ${answer} isn't empty. Pick a new name.`)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
folder = answer
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
rl.close()
|
|
96
130
|
}
|
|
97
131
|
|
|
98
132
|
if (Number(versions.node.split(".")[0]) < 20) fail(`Dashral needs Node.js 20 or newer (this is ${versions.node}).`)
|
|
@@ -123,23 +157,55 @@ if (own.release) {
|
|
|
123
157
|
}
|
|
124
158
|
if (!release) fail(`${url} has no releases yet.`)
|
|
125
159
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (run("git", ["switch", "--quiet", "-c", "main"], target) !== 0) fail("Couldn't start the main branch.")
|
|
131
|
-
|
|
132
|
-
if (own.install) {
|
|
133
|
-
console.log("\nInstalling the dependencies…")
|
|
134
|
-
if (run("pnpm", ["install"], target) !== 0) fail(`pnpm install stopped. Fix what it says, then in ${folder}: pnpm install && pnpm dashral init`)
|
|
160
|
+
let flat = !own.monorepo
|
|
161
|
+
if (flat && own.pro) {
|
|
162
|
+
console.log(" Dashral Pro starts as the monorepo for now (a project of its own is coming).")
|
|
163
|
+
flat = false
|
|
135
164
|
}
|
|
136
165
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
166
|
+
// Flat: the files are enough. The monorepo keeps the whole history: `pnpm dashral update` merges later releases into it.
|
|
167
|
+
console.log(`\nCloning Dashral ${release} into ${folder}…`)
|
|
168
|
+
const depth = flat ? ["--depth", "1"] : []
|
|
169
|
+
if (run("git", ["-c", "advice.detachedHead=false", "clone", "--quiet", ...depth, "--branch", release, url, target]) !== 0) fail(`Couldn't clone ${url}.${access}`)
|
|
170
|
+
if (flat && !readFileSync(join(target, "scripts/dashral.mjs"), "utf8").includes("--layout")) {
|
|
171
|
+
// A release from before the flat layout: set it up as the monorepo.
|
|
172
|
+
console.log(` ${release} predates the flat layout, so it's set up as the monorepo.`)
|
|
173
|
+
flat = false
|
|
174
|
+
rmSync(target, { recursive: true, force: true })
|
|
175
|
+
if (run("git", ["-c", "advice.detachedHead=false", "clone", "--quiet", "--branch", release, url, target]) !== 0) fail(`Couldn't clone ${url}.${access}`)
|
|
140
176
|
}
|
|
141
|
-
|
|
142
|
-
|
|
177
|
+
const restart = `It's a fresh copy, so you can delete ${folder} and start again.`
|
|
178
|
+
|
|
179
|
+
if (flat) {
|
|
180
|
+
// Setting up needs no dependencies: install once, in the project as it ends up.
|
|
181
|
+
console.log("")
|
|
182
|
+
if (run("node", ["scripts/dashral.mjs", "init", "--layout", "flat", ...rest], target) !== 0) fail(`The setup stopped. ${restart}`)
|
|
183
|
+
// The project's own history, from its first commit.
|
|
184
|
+
rmSync(join(target, ".git"), { recursive: true, force: true })
|
|
185
|
+
run("git", ["init", "--quiet", "-b", "main"], target)
|
|
186
|
+
if (own.install) {
|
|
187
|
+
console.log("\nInstalling the dependencies…")
|
|
188
|
+
if (run("pnpm", ["install"], target) !== 0) fail(`pnpm install stopped. Fix what it says, then run \`pnpm install\` in ${folder}.`)
|
|
189
|
+
}
|
|
190
|
+
run("git", ["add", "-A"], target, true)
|
|
191
|
+
const committed = run("git", ["commit", "--quiet", "-m", "Initial commit from create-dashral"], target, true) === 0
|
|
192
|
+
console.log(`
|
|
193
|
+
Your dashboard is in ${folder}:
|
|
194
|
+
cd ${folder}${own.install ? "" : "\n pnpm install"}
|
|
195
|
+
pnpm dev${committed ? "" : "\n\n(Git has no name and email here yet, so nothing was committed: commit when you're ready.)"}`)
|
|
196
|
+
} else {
|
|
197
|
+
// A tag checks out without a branch: start yours, so your commits have one.
|
|
198
|
+
if (run("git", ["switch", "--quiet", "-c", "main"], target) !== 0) fail("Couldn't start the main branch.")
|
|
199
|
+
if (own.install) {
|
|
200
|
+
console.log("\nInstalling the dependencies…")
|
|
201
|
+
if (run("pnpm", ["install"], target) !== 0) fail(`pnpm install stopped. Fix what it says, then in ${folder}: pnpm install && pnpm dashral init`)
|
|
202
|
+
}
|
|
203
|
+
console.log("")
|
|
204
|
+
if (run("node", ["scripts/dashral.mjs", "init", ...rest], target) !== 0) {
|
|
205
|
+
fail(`The setup stopped. ${restart} Or fix what it says and run \`pnpm dashral init\` in it.`)
|
|
206
|
+
}
|
|
207
|
+
console.log(`
|
|
143
208
|
Your dashboard is in ${folder}:
|
|
144
209
|
cd ${folder}${own.install ? "" : "\n pnpm install"}
|
|
145
210
|
pnpm dev`)
|
|
211
|
+
}
|