@vlandoss/vland 0.2.1-git-74f39bb.0 → 0.2.1-git-a1181c2.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.
- package/dist/cli.usage.kdl +34 -0
- package/dist/run.mjs +443 -0
- package/package.json +6 -11
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @generated by @usage-spec/commander from Commander.js metadata
|
|
2
|
+
name vland
|
|
3
|
+
bin vland
|
|
4
|
+
version "0.2.1-git-a1181c2.0"
|
|
5
|
+
usage "[options] [command]"
|
|
6
|
+
flag --usage help="print KDL spec for this CLI (https://kdl.dev)"
|
|
7
|
+
cmd completion help="print shell completion script 🐚 (usage)" {
|
|
8
|
+
long_help "Prints a shell completion script for vland. Add to your shell rc file:\n\n bash: eval \"$(vland completion bash)\"\n zsh: eval \"$(vland completion zsh)\"\n fish: vland completion fish | source"
|
|
9
|
+
arg <shell> help="target shell" {
|
|
10
|
+
choices bash zsh fish
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
cmd init help="init a new project 🚀 (giget)" {
|
|
14
|
+
long_help "Scaffold a new variableland project from one of the official templates."
|
|
15
|
+
flag "-t --template" help="template to use" {
|
|
16
|
+
arg <TEMPLATE> {
|
|
17
|
+
choices library backend monorepo
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
flag "-d --dir" help="target directory (default: ./<name>)" {
|
|
21
|
+
arg <DIR>
|
|
22
|
+
}
|
|
23
|
+
flag --pm help="package manager to use" {
|
|
24
|
+
arg <PM> {
|
|
25
|
+
choices npm pnpm yarn bun
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
flag --install help="install dependencies (skip prompt)"
|
|
29
|
+
flag --no-install help="skip dependency installation" negate=--install
|
|
30
|
+
flag --git help="initialise git repository (skip prompt)"
|
|
31
|
+
flag --no-git help="skip git init" negate=--git
|
|
32
|
+
flag "-f --force" help="overwrite existing directory"
|
|
33
|
+
arg "[name]" help="project name (also used as the target directory)" required=#false
|
|
34
|
+
}
|
package/dist/run.mjs
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import path, { extname, isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import { colorize, createPkg, createShellService, dirnameOf, hasTTY, palette, run, text } from "@vlandoss/clibuddy";
|
|
3
|
+
import { Argument, Command, Option, createCommand } from "commander";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { createLoggy } from "@vlandoss/loggy";
|
|
6
|
+
import { cp, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text as text$1 } from "@clack/prompts";
|
|
8
|
+
import { detectPackageManager, installDependencies } from "nypm";
|
|
9
|
+
import { downloadTemplate } from "giget";
|
|
10
|
+
import { generateToStdout } from "@usage-spec/commander";
|
|
11
|
+
//#region src/services/logger.ts
|
|
12
|
+
const logger = createLoggy({ namespace: "vland" });
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/services/ctx.ts
|
|
15
|
+
async function createContext(binDir) {
|
|
16
|
+
const debug = logger.subdebug("create-context");
|
|
17
|
+
const binPath = fs.realpathSync(binDir);
|
|
18
|
+
debug("bin path:", binPath);
|
|
19
|
+
const binPkg = await createPkg(binPath);
|
|
20
|
+
if (!binPkg) throw new Error("Could not find bin package.json");
|
|
21
|
+
debug("bin pkg info: %O", binPkg.info());
|
|
22
|
+
const shell = createShellService();
|
|
23
|
+
debug("shell service options: %O", shell.options);
|
|
24
|
+
return {
|
|
25
|
+
binPkg,
|
|
26
|
+
shell
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/program/ui.ts
|
|
31
|
+
const vlandColor = colorize("#a78bfa");
|
|
32
|
+
const usageColor = colorize("#24C55E");
|
|
33
|
+
const gigetColor = colorize("#F472B6");
|
|
34
|
+
const TOOL_LABELS = {
|
|
35
|
+
USAGE: usageColor("usage"),
|
|
36
|
+
GIGET: gigetColor("giget")
|
|
37
|
+
};
|
|
38
|
+
function getBannerText(version) {
|
|
39
|
+
return `
|
|
40
|
+
${vlandColor(`
|
|
41
|
+
██╗ ██╗██╗ █████╗ ███╗ ██╗██████╗
|
|
42
|
+
██║ ██║██║ ██╔══██╗████╗ ██║██╔══██╗
|
|
43
|
+
██║ ██║██║ ███████║██╔██╗ ██║██║ ██║
|
|
44
|
+
╚██╗ ██╔╝██║ ██╔══██║██║╚██╗██║██║ ██║
|
|
45
|
+
╚████╔╝ ███████╗██║ ██║██║ ╚████║██████╔╝
|
|
46
|
+
╚═══╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ${text.version(version)}
|
|
47
|
+
`.trim())}
|
|
48
|
+
|
|
49
|
+
🦉 ${palette.italic(palette.muted("The CLI to init a new project in"))} ${text.vland}\n`.trimStart();
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/program/commands/completion.ts
|
|
53
|
+
const SHELLS = [
|
|
54
|
+
"bash",
|
|
55
|
+
"zsh",
|
|
56
|
+
"fish"
|
|
57
|
+
];
|
|
58
|
+
function createCompletionCommand() {
|
|
59
|
+
return createCommand("completion").summary(`print shell completion script 🐚 (${TOOL_LABELS.USAGE})`).description(`Prints a shell completion script for vland. Add to your shell rc file:
|
|
60
|
+
|
|
61
|
+
bash: eval "$(vland completion bash)"
|
|
62
|
+
zsh: eval "$(vland completion zsh)"
|
|
63
|
+
fish: vland completion fish | source`).addArgument(new Argument("<shell>", `target shell`).choices(SHELLS)).addHelpText("afterAll", `\nUnder the hood, this command uses ${TOOL_LABELS.USAGE} (https://usage.jdx.dev).
|
|
64
|
+
Make sure to have it installed and available in your PATH.`);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/actions/placeholders.ts
|
|
68
|
+
const TEXT_EXTENSIONS = new Set([
|
|
69
|
+
".ts",
|
|
70
|
+
".tsx",
|
|
71
|
+
".js",
|
|
72
|
+
".jsx",
|
|
73
|
+
".mjs",
|
|
74
|
+
".cjs",
|
|
75
|
+
".mts",
|
|
76
|
+
".cts",
|
|
77
|
+
".json",
|
|
78
|
+
".jsonc",
|
|
79
|
+
".md",
|
|
80
|
+
".mdx",
|
|
81
|
+
".yml",
|
|
82
|
+
".yaml",
|
|
83
|
+
".toml",
|
|
84
|
+
".css",
|
|
85
|
+
".html",
|
|
86
|
+
".sh",
|
|
87
|
+
".env",
|
|
88
|
+
".gitignore",
|
|
89
|
+
".gitattributes",
|
|
90
|
+
".npmrc",
|
|
91
|
+
".nvmrc",
|
|
92
|
+
".node-version",
|
|
93
|
+
".dockerignore",
|
|
94
|
+
".editorconfig",
|
|
95
|
+
".prettierrc"
|
|
96
|
+
]);
|
|
97
|
+
const TEXT_FILENAMES = new Set([
|
|
98
|
+
"Dockerfile",
|
|
99
|
+
"LICENSE",
|
|
100
|
+
"README",
|
|
101
|
+
"CHANGELOG",
|
|
102
|
+
".gitignore",
|
|
103
|
+
".gitattributes",
|
|
104
|
+
".npmrc",
|
|
105
|
+
".nvmrc",
|
|
106
|
+
".node-version",
|
|
107
|
+
".dockerignore",
|
|
108
|
+
".editorconfig",
|
|
109
|
+
".prettierrc",
|
|
110
|
+
"lefthook.yml",
|
|
111
|
+
"mise.toml"
|
|
112
|
+
]);
|
|
113
|
+
const SKIP_DIRS = new Set([
|
|
114
|
+
"node_modules",
|
|
115
|
+
".git",
|
|
116
|
+
"dist",
|
|
117
|
+
".turbo",
|
|
118
|
+
".next",
|
|
119
|
+
"build",
|
|
120
|
+
"coverage"
|
|
121
|
+
]);
|
|
122
|
+
function isTextFile(name) {
|
|
123
|
+
if (TEXT_FILENAMES.has(name)) return true;
|
|
124
|
+
return TEXT_EXTENSIONS.has(extname(name));
|
|
125
|
+
}
|
|
126
|
+
async function walk(root, onFile) {
|
|
127
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
128
|
+
await Promise.all(entries.map(async (entry) => {
|
|
129
|
+
const full = join(root, entry.name);
|
|
130
|
+
if (entry.isDirectory()) {
|
|
131
|
+
if (SKIP_DIRS.has(entry.name)) return;
|
|
132
|
+
await walk(full, onFile);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (entry.isFile()) await onFile(full);
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
function applyPlaceholders(content, values) {
|
|
139
|
+
return content.replaceAll("{{projectName}}", values.projectName).replaceAll("{{author}}", values.author).replaceAll("{{year}}", values.year);
|
|
140
|
+
}
|
|
141
|
+
async function replacePlaceholders(rootDir, values) {
|
|
142
|
+
const debug = logger.subdebug("placeholders");
|
|
143
|
+
let touched = 0;
|
|
144
|
+
await walk(rootDir, async (filePath) => {
|
|
145
|
+
if (!isTextFile(filePath.split("/").pop() ?? "")) return;
|
|
146
|
+
if ((await stat(filePath)).size > 1e6) return;
|
|
147
|
+
const original = await readFile(filePath, "utf8");
|
|
148
|
+
const replaced = applyPlaceholders(original, values);
|
|
149
|
+
if (replaced !== original) {
|
|
150
|
+
await writeFile(filePath, replaced);
|
|
151
|
+
touched += 1;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
debug("placeholders applied to %d file(s)", touched);
|
|
155
|
+
return { touched };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Updates the root `package.json` `name` field via `pkg-types`. Safer than a
|
|
159
|
+
* regex pass because it preserves field ordering and JSON formatting handled
|
|
160
|
+
* by `pkg-types`.
|
|
161
|
+
*/
|
|
162
|
+
async function updateRootPackageName(rootDir, projectName) {
|
|
163
|
+
const debug = logger.subdebug("update-root-package-name");
|
|
164
|
+
const rootPath = fs.realpathSync(rootDir);
|
|
165
|
+
debug("root path:", rootPath);
|
|
166
|
+
debug("process cwd:", process.cwd());
|
|
167
|
+
const pkg = await createPkg(rootPath);
|
|
168
|
+
if (!pkg) throw new Error("Could not find package.json");
|
|
169
|
+
try {
|
|
170
|
+
pkg.packageJson.name = projectName;
|
|
171
|
+
await pkg.write(pkg.packageJson);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
debug("skipped %s", error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/actions/template.ts
|
|
178
|
+
const TEMPLATES = [
|
|
179
|
+
"library",
|
|
180
|
+
"backend",
|
|
181
|
+
"monorepo"
|
|
182
|
+
];
|
|
183
|
+
const TEMPLATE_META = {
|
|
184
|
+
library: {
|
|
185
|
+
placeholder: "my-lib",
|
|
186
|
+
runScript: "test"
|
|
187
|
+
},
|
|
188
|
+
backend: {
|
|
189
|
+
placeholder: "my-api",
|
|
190
|
+
runScript: "dev"
|
|
191
|
+
},
|
|
192
|
+
monorepo: {
|
|
193
|
+
placeholder: "my-mono",
|
|
194
|
+
runScript: "dev"
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const GITHUB_SOURCE = "github:variableland/dx";
|
|
198
|
+
const GITHUB_REF = "main";
|
|
199
|
+
/**
|
|
200
|
+
* Resolves the template into `dir`. Source order:
|
|
201
|
+
* 1. `VLAND_TEMPLATES_DIR` env var → copy from local path (used by E2E tests
|
|
202
|
+
* against the in-repo `templates/`).
|
|
203
|
+
* 2. Otherwise → download via giget from `github:variableland/dx/templates/<name>`.
|
|
204
|
+
*/
|
|
205
|
+
async function fetchTemplate(options) {
|
|
206
|
+
const debug = logger.subdebug("fetch-template");
|
|
207
|
+
const localRoot = process.env.VLAND_TEMPLATES_DIR;
|
|
208
|
+
if (localRoot) {
|
|
209
|
+
const sourceDir = resolve(localRoot, options.template);
|
|
210
|
+
debug("local source: %s", sourceDir);
|
|
211
|
+
await cp(sourceDir, options.dir, {
|
|
212
|
+
recursive: true,
|
|
213
|
+
force: options.force,
|
|
214
|
+
errorOnExist: !options.force,
|
|
215
|
+
filter: (src) => !src.includes("/node_modules") && !src.endsWith("/.turbo") && !src.endsWith("/dist")
|
|
216
|
+
});
|
|
217
|
+
return { source: sourceDir };
|
|
218
|
+
}
|
|
219
|
+
const source = `${GITHUB_SOURCE}/templates/${options.template}#${GITHUB_REF}`;
|
|
220
|
+
debug("remote source: %s", source);
|
|
221
|
+
return { source: (await downloadTemplate(source, {
|
|
222
|
+
dir: options.dir,
|
|
223
|
+
force: options.force,
|
|
224
|
+
install: false
|
|
225
|
+
})).source };
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/actions/init.ts
|
|
229
|
+
const NPM_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
230
|
+
function validateProjectName(name) {
|
|
231
|
+
if (!name || !name.trim()) return "Name is required.";
|
|
232
|
+
if (/\s/.test(name)) return "Name cannot contain whitespace.";
|
|
233
|
+
if (name.startsWith(".") || name.startsWith("/") || name.startsWith("\\")) return "Name cannot start with '.', '/' or '\\'.";
|
|
234
|
+
if (name.includes("..")) return "Name cannot contain '..'.";
|
|
235
|
+
if (!NPM_NAME_RE.test(name)) return "Name must be a valid npm package name (lowercase, no spaces).";
|
|
236
|
+
}
|
|
237
|
+
async function isDirEmpty(dir) {
|
|
238
|
+
try {
|
|
239
|
+
return (await readdir(dir)).length === 0;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error.code === "ENOENT") return true;
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function readGitAuthor(shell) {
|
|
246
|
+
try {
|
|
247
|
+
const [name, email] = await Promise.all([shell.runCaptured("git", [
|
|
248
|
+
"config",
|
|
249
|
+
"--get",
|
|
250
|
+
"user.name"
|
|
251
|
+
], { throwOnError: false }), shell.runCaptured("git", [
|
|
252
|
+
"config",
|
|
253
|
+
"--get",
|
|
254
|
+
"user.email"
|
|
255
|
+
], { throwOnError: false })]);
|
|
256
|
+
const trimmedName = name.stdout.trim();
|
|
257
|
+
const trimmedEmail = email.stdout.trim();
|
|
258
|
+
if (!trimmedName) return void 0;
|
|
259
|
+
return trimmedEmail ? `${trimmedName} <${trimmedEmail}>` : trimmedName;
|
|
260
|
+
} catch {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function abort(message) {
|
|
265
|
+
cancel(message);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
async function runInit(ctx, options) {
|
|
269
|
+
const debug = logger.subdebug("init");
|
|
270
|
+
debug("options: %O", options);
|
|
271
|
+
const shell = ctx.shell;
|
|
272
|
+
intro(`${palette.label(" vland init ")}`);
|
|
273
|
+
let template = options.template;
|
|
274
|
+
if (!template) {
|
|
275
|
+
if (!hasTTY) abort("Template is required in non-interactive environments. Use --template <library|backend|monorepo>.");
|
|
276
|
+
const choice = await select({
|
|
277
|
+
message: "Pick a template",
|
|
278
|
+
options: TEMPLATES.map((value) => ({
|
|
279
|
+
value,
|
|
280
|
+
label: value
|
|
281
|
+
}))
|
|
282
|
+
});
|
|
283
|
+
if (isCancel(choice)) abort("Cancelled.");
|
|
284
|
+
template = choice;
|
|
285
|
+
}
|
|
286
|
+
let name = options.name;
|
|
287
|
+
if (!name) {
|
|
288
|
+
if (!hasTTY) abort("Project name is required in non-interactive environments. Pass it as the first argument.");
|
|
289
|
+
const value = await text$1({
|
|
290
|
+
message: "Project name",
|
|
291
|
+
placeholder: TEMPLATE_META[template].placeholder,
|
|
292
|
+
validate: (input) => validateProjectName(input ?? "")
|
|
293
|
+
});
|
|
294
|
+
if (isCancel(value)) abort("Cancelled.");
|
|
295
|
+
name = value;
|
|
296
|
+
}
|
|
297
|
+
const nameError = validateProjectName(name);
|
|
298
|
+
if (nameError) abort(nameError);
|
|
299
|
+
const dir = options.dir ? isAbsolute(options.dir) ? options.dir : resolve(process.cwd(), options.dir) : resolve(process.cwd(), name);
|
|
300
|
+
debug("target dir: %s", dir);
|
|
301
|
+
if (!await isDirEmpty(dir) && !options.force) abort(`Target directory ${palette.highlight(dir)} is not empty. Re-run with ${palette.highlight("--force")} to overwrite.`);
|
|
302
|
+
let author = await readGitAuthor(shell);
|
|
303
|
+
if (!author) if (!hasTTY) author = "";
|
|
304
|
+
else {
|
|
305
|
+
const value = await text$1({
|
|
306
|
+
message: "Author (used in package.json / LICENSE)",
|
|
307
|
+
placeholder: "Jane Doe <jane@example.com>",
|
|
308
|
+
defaultValue: ""
|
|
309
|
+
});
|
|
310
|
+
if (isCancel(value)) abort("Cancelled.");
|
|
311
|
+
author = value || "";
|
|
312
|
+
}
|
|
313
|
+
debug("author: %s", author || "<empty>");
|
|
314
|
+
const fetchSpin = spinner();
|
|
315
|
+
fetchSpin.start(`Fetching ${palette.highlight(template)} template`);
|
|
316
|
+
try {
|
|
317
|
+
const { source } = await fetchTemplate({
|
|
318
|
+
template,
|
|
319
|
+
dir,
|
|
320
|
+
force: options.force
|
|
321
|
+
});
|
|
322
|
+
fetchSpin.stop(`Fetched template from ${palette.muted(source)}`);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
fetchSpin.stop("Failed to fetch template", 1);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
const placeholderSpin = spinner();
|
|
328
|
+
placeholderSpin.start("Applying placeholders");
|
|
329
|
+
await replacePlaceholders(dir, {
|
|
330
|
+
projectName: name,
|
|
331
|
+
author,
|
|
332
|
+
year: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
333
|
+
});
|
|
334
|
+
await updateRootPackageName(dir, name);
|
|
335
|
+
placeholderSpin.stop("Placeholders applied");
|
|
336
|
+
const shouldInstall = await resolveYesNo(options.install, "Install dependencies?");
|
|
337
|
+
const shouldGit = await resolveYesNo(options.git, "Initialise a git repository?");
|
|
338
|
+
if (shouldInstall) {
|
|
339
|
+
const detected = options.pm ?? (await detectPackageManager(dir, { ignorePackageJSON: false }))?.name ?? "pnpm";
|
|
340
|
+
const installSpin = spinner();
|
|
341
|
+
installSpin.start(`Installing dependencies with ${palette.highlight(detected)}`);
|
|
342
|
+
try {
|
|
343
|
+
await installDependencies({
|
|
344
|
+
cwd: dir,
|
|
345
|
+
packageManager: {
|
|
346
|
+
name: detected,
|
|
347
|
+
command: detected
|
|
348
|
+
},
|
|
349
|
+
silent: true
|
|
350
|
+
});
|
|
351
|
+
installSpin.stop(`Installed with ${palette.highlight(detected)}`);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
installSpin.stop("Failed to install dependencies", 1);
|
|
354
|
+
log.warn("You can install manually later with `cd <dir> && <pm> install`.");
|
|
355
|
+
debug("install error: %O", error);
|
|
356
|
+
}
|
|
357
|
+
} else log.info(`Skipping ${palette.highlight("install")}.`);
|
|
358
|
+
if (shouldGit) {
|
|
359
|
+
const gitSpin = spinner();
|
|
360
|
+
gitSpin.start("Initialising git repository");
|
|
361
|
+
try {
|
|
362
|
+
const gitShell = shell.at(dir).child({ env: {
|
|
363
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "vland",
|
|
364
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "noreply@variable.land",
|
|
365
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "vland",
|
|
366
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "noreply@variable.land"
|
|
367
|
+
} });
|
|
368
|
+
await gitShell.runCaptured("git", ["init"]);
|
|
369
|
+
await gitShell.runCaptured("git", ["add", "-A"]);
|
|
370
|
+
await gitShell.runCaptured("git", [
|
|
371
|
+
"commit",
|
|
372
|
+
"-m",
|
|
373
|
+
"chore: initial commit from vland"
|
|
374
|
+
]);
|
|
375
|
+
gitSpin.stop("Initialised git repository");
|
|
376
|
+
} catch (error) {
|
|
377
|
+
gitSpin.stop("Failed to initialise git", 1);
|
|
378
|
+
debug("git error: %O", error);
|
|
379
|
+
}
|
|
380
|
+
} else log.info(`Skipping ${palette.highlight("git init")}.`);
|
|
381
|
+
const detectedPm = options.pm ?? (await detectPackageManager(dir, { ignorePackageJSON: false }))?.name ?? "pnpm";
|
|
382
|
+
const runScript = TEMPLATE_META[template].runScript;
|
|
383
|
+
outro([
|
|
384
|
+
palette.success("Done!"),
|
|
385
|
+
"",
|
|
386
|
+
palette.muted("Next steps:"),
|
|
387
|
+
` cd ${name}`,
|
|
388
|
+
shouldInstall ? ` ${detectedPm} ${runScript}` : ` ${detectedPm} install && ${detectedPm} ${runScript}`
|
|
389
|
+
].join("\n"));
|
|
390
|
+
}
|
|
391
|
+
async function resolveYesNo(explicit, message) {
|
|
392
|
+
if (typeof explicit === "boolean") return explicit;
|
|
393
|
+
if (!hasTTY) return true;
|
|
394
|
+
const value = await confirm({
|
|
395
|
+
message,
|
|
396
|
+
initialValue: true
|
|
397
|
+
});
|
|
398
|
+
if (isCancel(value)) abort("Cancelled.");
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/program/commands/init.ts
|
|
403
|
+
function createInitCommand(ctx) {
|
|
404
|
+
return createCommand("init").summary(`init a new project 🚀 (${TOOL_LABELS.GIGET})`).description("Scaffold a new variableland project from one of the official templates.").addArgument(new Argument("[name]", "project name (also used as the target directory)")).addOption(new Option("-t, --template <name>", "template to use").choices([...TEMPLATES])).addOption(new Option("-d, --dir <path>", "target directory (default: ./<name>)")).addOption(new Option("--pm <manager>", "package manager to use").choices([
|
|
405
|
+
"npm",
|
|
406
|
+
"pnpm",
|
|
407
|
+
"yarn",
|
|
408
|
+
"bun"
|
|
409
|
+
])).addOption(new Option("--install", "install dependencies (skip prompt)")).addOption(new Option("--no-install", "skip dependency installation")).addOption(new Option("--git", "initialise git repository (skip prompt)")).addOption(new Option("--no-git", "skip git init")).addOption(new Option("-f, --force", "overwrite existing directory").default(false)).action(async function(name, options) {
|
|
410
|
+
console.log(getBannerText(ctx.binPkg.version));
|
|
411
|
+
const installSource = this.getOptionValueSource("install");
|
|
412
|
+
const gitSource = this.getOptionValueSource("git");
|
|
413
|
+
await runInit(ctx, {
|
|
414
|
+
name,
|
|
415
|
+
...options,
|
|
416
|
+
install: installSource === "cli" ? options.install : void 0,
|
|
417
|
+
git: gitSource === "cli" ? options.git : void 0
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/program/commands/usage.ts
|
|
423
|
+
function addUsage(program) {
|
|
424
|
+
return program.addOption(new Option("--usage", "print KDL spec for this CLI (https://kdl.dev)")).on("option:usage", () => {
|
|
425
|
+
generateToStdout(program);
|
|
426
|
+
process.exit(0);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/program/index.ts
|
|
431
|
+
async function createProgram(options) {
|
|
432
|
+
const ctx = await createContext(options.binDir);
|
|
433
|
+
const version = ctx.binPkg.version;
|
|
434
|
+
return addUsage(new Command("vland").version(version, "-v, --version").addHelpText("before", getBannerText(version)).addCommand(createCompletionCommand()).addCommand(createInitCommand(ctx)));
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/run.ts
|
|
438
|
+
const BIN_DIR = path.dirname(dirnameOf(import.meta));
|
|
439
|
+
await run(async () => {
|
|
440
|
+
await (await createProgram({ binDir: BIN_DIR })).parseAsync();
|
|
441
|
+
}, logger);
|
|
442
|
+
//#endregion
|
|
443
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vlandoss/vland",
|
|
3
|
-
"version": "0.2.1-git-
|
|
3
|
+
"version": "0.2.1-git-a1181c2.0",
|
|
4
4
|
"description": "The CLI to init a new project in Variable Land",
|
|
5
5
|
"homepage": "https://github.com/variableland/dx/tree/main/packages/vland#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
"directory": "packages/vland"
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
|
-
"author":
|
|
16
|
-
"name": "rcrd",
|
|
17
|
-
"email": "rcrd@variable.land"
|
|
18
|
-
},
|
|
15
|
+
"author": "rcrd <rcrd@variable.land>",
|
|
19
16
|
"type": "module",
|
|
20
17
|
"imports": {
|
|
21
18
|
"#src/*": "./src/*",
|
|
@@ -35,11 +32,11 @@
|
|
|
35
32
|
"dependencies": {
|
|
36
33
|
"@clack/prompts": "0.11.0",
|
|
37
34
|
"@usage-spec/commander": "1.1.0",
|
|
38
|
-
"@vlandoss/clibuddy": "0.6.1-git-74f39bb.0",
|
|
39
|
-
"@vlandoss/loggy": "0.2.1-git-74f39bb.0",
|
|
40
35
|
"commander": "14.0.3",
|
|
41
36
|
"giget": "2.0.0",
|
|
42
|
-
"nypm": "0.6.0"
|
|
37
|
+
"nypm": "0.6.0",
|
|
38
|
+
"@vlandoss/clibuddy": "0.6.1-git-a1181c2.0",
|
|
39
|
+
"@vlandoss/loggy": "0.2.1-git-a1181c2.0"
|
|
43
40
|
},
|
|
44
41
|
"publishConfig": {
|
|
45
42
|
"access": "public"
|
|
@@ -50,8 +47,6 @@
|
|
|
50
47
|
"devDependencies": {
|
|
51
48
|
"@vlandoss/tsdown-config": "^0.0.1"
|
|
52
49
|
},
|
|
53
|
-
"readme": "ERROR: No README data found!",
|
|
54
|
-
"_id": "@vlandoss/vland@0.2.0",
|
|
55
50
|
"scripts": {
|
|
56
51
|
"build": "tsdown && pnpm build:kdl",
|
|
57
52
|
"build:kdl": "./bin --usage > dist/cli.usage.kdl",
|
|
@@ -60,4 +55,4 @@
|
|
|
60
55
|
"test:integration": "vitest run --project integration",
|
|
61
56
|
"test:types": "rr tsc"
|
|
62
57
|
}
|
|
63
|
-
}
|
|
58
|
+
}
|