@variablelab/vland 0.3.6 → 0.3.8-git-fdf054c.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/README.md +7 -5
- package/dist/cli.usage.kdl +2 -2
- package/dist/run.mjs +81 -36
- package/package.json +3 -3
- package/src/actions/init.ts +48 -18
- package/src/actions/placeholders.ts +22 -19
- package/src/actions/template.ts +10 -0
- package/src/program/commands/completion.ts +13 -4
package/README.md
CHANGED
|
@@ -43,23 +43,25 @@ See [`CLI.md`](./CLI.md) for the full reference (auto-generated per release).
|
|
|
43
43
|
| `backend` | An Elysia (`@elysiajs/node`) backend with evlog, Vitest, Dockerfile, CI shape. |
|
|
44
44
|
| `monorepo` | pnpm + Turbo workspace with an Elysia API, a Vite-React SPA, and a few internal packages. |
|
|
45
45
|
|
|
46
|
-
All templates target Node.js,
|
|
46
|
+
All templates target Node.js, pin Node.js and pnpm with [mise](https://mise.jdx.dev) (`mise.toml` and `mise.lock`), run their GitHub Actions on SHA-pinned actions checked by [zizmor](https://docs.zizmor.sh), and ship [`@rrlab/cli`](https://github.com/withvariablelab/dx/tree/main/run-run) (`rr`) as the single entry point for lint, format, type-check, and build. The per-tool config files (`biome.json`, `tsconfig.json`, `tsdown.config.ts`) are not bundled — run `rr plugins add biome ts tsdown` (one at a time) in your new project to opt in. See the scaffolded README for the exact post-install setup.
|
|
47
47
|
|
|
48
48
|
## Shell completion
|
|
49
49
|
|
|
50
|
-
`vland` ships a `completion` subcommand that prints a shell-specific script. Add
|
|
50
|
+
`vland` ships a `completion` subcommand that prints a shell-specific script. Add a guarded eval to your shell rc file:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
53
|
# zsh — ~/.zshrc
|
|
54
|
-
eval "$(vland completion zsh)"
|
|
54
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion zsh 2>/dev/null)"
|
|
55
55
|
|
|
56
56
|
# bash — ~/.bashrc
|
|
57
|
-
eval "$(vland completion bash)"
|
|
57
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion bash 2>/dev/null)"
|
|
58
58
|
|
|
59
59
|
# fish — ~/.config/fish/config.fish
|
|
60
|
-
vland completion fish | source
|
|
60
|
+
command -v vland >/dev/null 2>&1; and vland completion fish 2>/dev/null | source
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
The `command -v` guard skips the line when no `vland` is on `PATH`. The `2>/dev/null` covers a mise shim with no active version: it passes the guard, then fails on stderr. If completion doesn't load, run `vland completion zsh` by hand to see why.
|
|
64
|
+
|
|
63
65
|
**Prerequisite:** the [`usage`](https://usage.jdx.dev) CLI must be on your `PATH` (it powers completion at runtime). Install via one of:
|
|
64
66
|
|
|
65
67
|
```sh
|
package/dist/cli.usage.kdl
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @generated by @usage-spec/commander from Commander.js metadata
|
|
2
2
|
name vland
|
|
3
3
|
bin vland
|
|
4
|
-
version "0.3.
|
|
4
|
+
version "0.3.8-git-fdf054c.0"
|
|
5
5
|
usage "[options] [command]"
|
|
6
6
|
flag --usage help="print KDL spec for this CLI (https://kdl.dev)"
|
|
7
7
|
cmd completion help="print shell completion script (usage)" {
|
|
8
|
-
long_help "Prints a shell completion script for vland. Add
|
|
8
|
+
long_help "Prints a shell completion script for vland. Add your shell's line to its rc file:\n\n # bash — ~/.bashrc\n command -v vland >/dev/null 2>&1 && eval \"$(vland completion bash 2>/dev/null)\"\n\n # zsh — ~/.zshrc\n command -v vland >/dev/null 2>&1 && eval \"$(vland completion zsh 2>/dev/null)\"\n\n # fish — ~/.config/fish/config.fish\n command -v vland >/dev/null 2>&1; and vland completion fish 2>/dev/null | source"
|
|
9
9
|
arg <shell> help="target shell" {
|
|
10
10
|
choices bash zsh fish
|
|
11
11
|
}
|
package/dist/run.mjs
CHANGED
|
@@ -56,11 +56,16 @@ const SHELLS = [
|
|
|
56
56
|
"fish"
|
|
57
57
|
];
|
|
58
58
|
function createCompletionCommand() {
|
|
59
|
-
return createCommand("completion").summary(`print shell completion script (${TOOL_LABELS.USAGE})`).description(`Prints a shell completion script for vland. Add
|
|
59
|
+
return createCommand("completion").summary(`print shell completion script (${TOOL_LABELS.USAGE})`).description(`Prints a shell completion script for vland. Add your shell's line to its rc file:
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# bash — ~/.bashrc
|
|
62
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion bash 2>/dev/null)"
|
|
63
|
+
|
|
64
|
+
# zsh — ~/.zshrc
|
|
65
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion zsh 2>/dev/null)"
|
|
66
|
+
|
|
67
|
+
# fish — ~/.config/fish/config.fish
|
|
68
|
+
command -v vland >/dev/null 2>&1; and vland completion fish 2>/dev/null | source`).configureHelp({ helpWidth: Number.POSITIVE_INFINITY }).addArgument(new Argument("<shell>", `target shell`).choices(SHELLS)).addHelpText("afterAll", `\nUnder the hood, this command uses ${TOOL_LABELS.USAGE} (https://usage.jdx.dev).
|
|
64
69
|
Make sure to have it installed and available in your PATH.`);
|
|
65
70
|
}
|
|
66
71
|
//#endregion
|
|
@@ -94,6 +99,15 @@ const LIBRARY_SCOPES = {
|
|
|
94
99
|
private: "@variableland",
|
|
95
100
|
public: "@variablelab"
|
|
96
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* The GitHub owner of a project's repository, indexed by visibility: public
|
|
104
|
+
* projects live in the open-source `withvariablelab` org, private ones in
|
|
105
|
+
* `variableland`.
|
|
106
|
+
*/
|
|
107
|
+
const GITHUB_OWNERS = {
|
|
108
|
+
private: "variableland",
|
|
109
|
+
public: "withvariablelab"
|
|
110
|
+
};
|
|
97
111
|
const GITHUB_SOURCE = "github:withvariablelab/dx";
|
|
98
112
|
const GITHUB_REF = "main";
|
|
99
113
|
/**
|
|
@@ -197,7 +211,7 @@ async function walk(root, onFile) {
|
|
|
197
211
|
}));
|
|
198
212
|
}
|
|
199
213
|
function applyPlaceholders(content, values) {
|
|
200
|
-
return content.replaceAll("{{projectName}}", values.projectName).replaceAll("{{author}}", values.author).replaceAll("{{year}}", values.year);
|
|
214
|
+
return content.replaceAll("{{projectName}}", values.projectName).replaceAll("{{author}}", values.author).replaceAll("{{year}}", values.year).replaceAll("{{packageName}}", values.packageName).replaceAll("{{githubOwner}}", values.githubOwner);
|
|
201
215
|
}
|
|
202
216
|
async function replacePlaceholders(rootDir, values) {
|
|
203
217
|
const debug = logger.subdebug("placeholders");
|
|
@@ -216,30 +230,29 @@ async function replacePlaceholders(rootDir, values) {
|
|
|
216
230
|
return { touched };
|
|
217
231
|
}
|
|
218
232
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* JSON formatting survive.
|
|
222
|
-
*
|
|
223
|
-
* Naming rules:
|
|
224
|
-
* - `library` private → `@variableland/<projectName>`, `private: true`
|
|
233
|
+
* The root package's name:
|
|
234
|
+
* - `library` private → `@variableland/<projectName>`
|
|
225
235
|
* - `library` public → `@variablelab/<projectName>`
|
|
226
|
-
* - `backend`
|
|
227
|
-
|
|
236
|
+
* - `backend`, `monorepo` → `<projectName>`
|
|
237
|
+
*/
|
|
238
|
+
function rootPackageName({ template, projectName, visibility }) {
|
|
239
|
+
return template === "library" ? `${LIBRARY_SCOPES[visibility ?? "public"]}/${projectName}` : projectName;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Writes the root `package.json`'s `name` ({@link rootPackageName}) and
|
|
243
|
+
* `private` fields based on the chosen template and visibility. Uses
|
|
244
|
+
* `pkg-types` so field ordering and JSON formatting survive. `private: true`
|
|
245
|
+
* goes on a private library and on every backend and monorepo root, which
|
|
246
|
+
* never publishes.
|
|
228
247
|
*/
|
|
229
248
|
async function applyRootPackage(rootDir, options) {
|
|
230
249
|
const debug = logger.subdebug("apply-root-package");
|
|
231
250
|
const pkg = await createPkg(fs.realpathSync(rootDir));
|
|
232
251
|
if (!pkg) throw new Error("Could not find package.json");
|
|
233
|
-
const { template,
|
|
252
|
+
const { template, visibility } = options;
|
|
234
253
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
pkg.packageJson.name = `${scope}/${projectName}`;
|
|
238
|
-
if (visibility === "private") pkg.packageJson.private = true;
|
|
239
|
-
} else {
|
|
240
|
-
pkg.packageJson.name = projectName;
|
|
241
|
-
pkg.packageJson.private = true;
|
|
242
|
-
}
|
|
254
|
+
pkg.packageJson.name = rootPackageName(options);
|
|
255
|
+
if (template !== "library" || visibility === "private") pkg.packageJson.private = true;
|
|
243
256
|
await pkg.write(pkg.packageJson);
|
|
244
257
|
} catch (error) {
|
|
245
258
|
debug("skipped %s", error);
|
|
@@ -264,7 +277,12 @@ async function isDirEmpty(dir) {
|
|
|
264
277
|
throw error;
|
|
265
278
|
}
|
|
266
279
|
}
|
|
267
|
-
|
|
280
|
+
const FALLBACK_GIT_IDENTITY = {
|
|
281
|
+
name: "vland",
|
|
282
|
+
email: "noreply@variable.land"
|
|
283
|
+
};
|
|
284
|
+
/** The `user.name` and `user.email` git resolves from the shell's cwd. Each is empty when unset. */
|
|
285
|
+
async function readGitIdentity(shell) {
|
|
268
286
|
try {
|
|
269
287
|
const [name, email] = await Promise.all([shell.runCaptured("git", [
|
|
270
288
|
"config",
|
|
@@ -275,14 +293,31 @@ async function readGitAuthor(shell) {
|
|
|
275
293
|
"--get",
|
|
276
294
|
"user.email"
|
|
277
295
|
], { throwOnError: false })]);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
296
|
+
return {
|
|
297
|
+
name: name.stdout.trim(),
|
|
298
|
+
email: email.stdout.trim()
|
|
299
|
+
};
|
|
282
300
|
} catch {
|
|
283
|
-
return
|
|
301
|
+
return {
|
|
302
|
+
name: "",
|
|
303
|
+
email: ""
|
|
304
|
+
};
|
|
284
305
|
}
|
|
285
306
|
}
|
|
307
|
+
async function readGitAuthor(shell) {
|
|
308
|
+
const { name, email } = await readGitIdentity(shell);
|
|
309
|
+
if (!name) return void 0;
|
|
310
|
+
return email ? `${name} <${email}>` : name;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* `-c` flags that fill in only the half of the identity git can't resolve. A
|
|
314
|
+
* configured identity is left alone, so `commit.gpgsign` signs with the user's
|
|
315
|
+
* own email.
|
|
316
|
+
*/
|
|
317
|
+
async function gitIdentityFallbackArgs(shell) {
|
|
318
|
+
const { name, email } = await readGitIdentity(shell);
|
|
319
|
+
return [...name ? [] : ["-c", `user.name=${FALLBACK_GIT_IDENTITY.name}`], ...email ? [] : ["-c", `user.email=${FALLBACK_GIT_IDENTITY.email}`]];
|
|
320
|
+
}
|
|
286
321
|
function abort(message) {
|
|
287
322
|
cancel(message);
|
|
288
323
|
process.exit(1);
|
|
@@ -368,7 +403,13 @@ async function runInit(ctx, options) {
|
|
|
368
403
|
await replacePlaceholders(dir, {
|
|
369
404
|
projectName: name,
|
|
370
405
|
author,
|
|
371
|
-
year: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
406
|
+
year: (/* @__PURE__ */ new Date()).getFullYear().toString(),
|
|
407
|
+
packageName: rootPackageName({
|
|
408
|
+
template,
|
|
409
|
+
projectName: name,
|
|
410
|
+
visibility
|
|
411
|
+
}),
|
|
412
|
+
githubOwner: GITHUB_OWNERS[template === "library" ? visibility ?? "public" : "private"]
|
|
372
413
|
});
|
|
373
414
|
await applyRootPackage(dir, {
|
|
374
415
|
template,
|
|
@@ -378,6 +419,13 @@ async function runInit(ctx, options) {
|
|
|
378
419
|
placeholderSpin.stop("Placeholders applied");
|
|
379
420
|
const shouldInstall = await resolveYesNo(options.install, "Install dependencies?");
|
|
380
421
|
const shouldGit = await resolveYesNo(options.git, "Initialise a git repository?");
|
|
422
|
+
const gitShell = shell.at(dir);
|
|
423
|
+
let gitInitError;
|
|
424
|
+
if (shouldGit) try {
|
|
425
|
+
await gitShell.runCaptured("git", ["init"]);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
gitInitError = error;
|
|
428
|
+
}
|
|
381
429
|
if (shouldInstall) {
|
|
382
430
|
const installSpin = spinner();
|
|
383
431
|
installSpin.start(`Installing dependencies with ${palette.highlight(PACKAGE_MANAGER)}`);
|
|
@@ -401,16 +449,13 @@ async function runInit(ctx, options) {
|
|
|
401
449
|
const gitSpin = spinner();
|
|
402
450
|
gitSpin.start("Initialising git repository");
|
|
403
451
|
try {
|
|
404
|
-
|
|
405
|
-
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "vland",
|
|
406
|
-
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "noreply@variable.land",
|
|
407
|
-
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "vland",
|
|
408
|
-
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "noreply@variable.land"
|
|
409
|
-
} });
|
|
410
|
-
await gitShell.runCaptured("git", ["init"]);
|
|
452
|
+
if (gitInitError) throw gitInitError;
|
|
411
453
|
await gitShell.runCaptured("git", ["add", "-A"]);
|
|
454
|
+
const identityArgs = await gitIdentityFallbackArgs(gitShell);
|
|
412
455
|
await gitShell.runCaptured("git", [
|
|
456
|
+
...identityArgs,
|
|
413
457
|
"commit",
|
|
458
|
+
"--no-verify",
|
|
414
459
|
"-m",
|
|
415
460
|
"chore: initial commit from vland"
|
|
416
461
|
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variablelab/vland",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8-git-fdf054c.0",
|
|
4
4
|
"description": "The CLI to init a new project in Variable Land",
|
|
5
5
|
"homepage": "https://github.com/withvariablelab/dx/tree/main/vland/cli#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"commander": "14.0.3",
|
|
36
36
|
"giget": "2.0.0",
|
|
37
37
|
"nypm": "0.6.0",
|
|
38
|
-
"@variablelab/
|
|
39
|
-
"@variablelab/
|
|
38
|
+
"@variablelab/loggy": "0.2.3",
|
|
39
|
+
"@variablelab/clibuddy": "0.7.4"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
package/src/actions/init.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { hasTTY, palette } from "@variablelab/clibuddy";
|
|
|
5
5
|
import { installDependencies } from "nypm";
|
|
6
6
|
import type { Context } from "#src/services/ctx.ts";
|
|
7
7
|
import { logger } from "#src/services/logger.ts";
|
|
8
|
-
import { applyRootPackage, replacePlaceholders } from "./placeholders.ts";
|
|
9
|
-
import { fetchTemplate, TEMPLATE_META, TEMPLATES, type TemplateName, type Visibility } from "./template.ts";
|
|
8
|
+
import { applyRootPackage, replacePlaceholders, rootPackageName } from "./placeholders.ts";
|
|
9
|
+
import { fetchTemplate, GITHUB_OWNERS, TEMPLATE_META, TEMPLATES, type TemplateName, type Visibility } from "./template.ts";
|
|
10
10
|
|
|
11
11
|
export type InitOptions = {
|
|
12
12
|
name?: string;
|
|
@@ -45,21 +45,42 @@ async function isDirEmpty(dir: string): Promise<boolean> {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
type GitIdentity = { name: string; email: string };
|
|
49
|
+
|
|
50
|
+
const FALLBACK_GIT_IDENTITY: GitIdentity = { name: "vland", email: "noreply@variable.land" };
|
|
51
|
+
|
|
52
|
+
/** The `user.name` and `user.email` git resolves from the shell's cwd. Each is empty when unset. */
|
|
53
|
+
async function readGitIdentity(shell: Context["shell"]): Promise<GitIdentity> {
|
|
49
54
|
try {
|
|
50
55
|
const [name, email] = await Promise.all([
|
|
51
56
|
shell.runCaptured("git", ["config", "--get", "user.name"], { throwOnError: false }),
|
|
52
57
|
shell.runCaptured("git", ["config", "--get", "user.email"], { throwOnError: false }),
|
|
53
58
|
]);
|
|
54
|
-
|
|
55
|
-
const trimmedEmail = email.stdout.trim();
|
|
56
|
-
if (!trimmedName) return undefined;
|
|
57
|
-
return trimmedEmail ? `${trimmedName} <${trimmedEmail}>` : trimmedName;
|
|
59
|
+
return { name: name.stdout.trim(), email: email.stdout.trim() };
|
|
58
60
|
} catch {
|
|
59
|
-
return
|
|
61
|
+
return { name: "", email: "" };
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
async function readGitAuthor(shell: Context["shell"]): Promise<string | undefined> {
|
|
66
|
+
const { name, email } = await readGitIdentity(shell);
|
|
67
|
+
if (!name) return undefined;
|
|
68
|
+
return email ? `${name} <${email}>` : name;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `-c` flags that fill in only the half of the identity git can't resolve. A
|
|
73
|
+
* configured identity is left alone, so `commit.gpgsign` signs with the user's
|
|
74
|
+
* own email.
|
|
75
|
+
*/
|
|
76
|
+
async function gitIdentityFallbackArgs(shell: Context["shell"]): Promise<string[]> {
|
|
77
|
+
const { name, email } = await readGitIdentity(shell);
|
|
78
|
+
return [
|
|
79
|
+
...(name ? [] : ["-c", `user.name=${FALLBACK_GIT_IDENTITY.name}`]),
|
|
80
|
+
...(email ? [] : ["-c", `user.email=${FALLBACK_GIT_IDENTITY.email}`]),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
function abort(message: string): never {
|
|
64
85
|
cancel(message);
|
|
65
86
|
process.exit(1);
|
|
@@ -167,6 +188,9 @@ export async function runInit(ctx: Context, options: InitOptions) {
|
|
|
167
188
|
projectName: name,
|
|
168
189
|
author,
|
|
169
190
|
year: new Date().getFullYear().toString(),
|
|
191
|
+
packageName: rootPackageName({ template, projectName: name, visibility }),
|
|
192
|
+
// Only a library picks its visibility: backend and monorepo roots are always private.
|
|
193
|
+
githubOwner: GITHUB_OWNERS[template === "library" ? (visibility ?? "public") : "private"],
|
|
170
194
|
});
|
|
171
195
|
await applyRootPackage(dir, { template, projectName: name, visibility });
|
|
172
196
|
placeholderSpin.stop("Placeholders applied");
|
|
@@ -174,6 +198,17 @@ export async function runInit(ctx: Context, options: InitOptions) {
|
|
|
174
198
|
const shouldInstall = await resolveYesNo(options.install, "Install dependencies?");
|
|
175
199
|
const shouldGit = await resolveYesNo(options.git, "Initialise a git repository?");
|
|
176
200
|
|
|
201
|
+
const gitShell = shell.at(dir);
|
|
202
|
+
let gitInitError: unknown;
|
|
203
|
+
if (shouldGit) {
|
|
204
|
+
// Before the install, so a postinstall that sets up git hooks (lefthook's) finds the repo.
|
|
205
|
+
try {
|
|
206
|
+
await gitShell.runCaptured("git", ["init"]);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
gitInitError = error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
177
212
|
if (shouldInstall) {
|
|
178
213
|
const installSpin = spinner();
|
|
179
214
|
installSpin.start(`Installing dependencies with ${palette.highlight(PACKAGE_MANAGER)}`);
|
|
@@ -197,17 +232,12 @@ export async function runInit(ctx: Context, options: InitOptions) {
|
|
|
197
232
|
const gitSpin = spinner();
|
|
198
233
|
gitSpin.start("Initialising git repository");
|
|
199
234
|
try {
|
|
200
|
-
|
|
201
|
-
env: {
|
|
202
|
-
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "vland",
|
|
203
|
-
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "noreply@variable.land",
|
|
204
|
-
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "vland",
|
|
205
|
-
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "noreply@variable.land",
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
await gitShell.runCaptured("git", ["init"]);
|
|
235
|
+
if (gitInitError) throw gitInitError;
|
|
209
236
|
await gitShell.runCaptured("git", ["add", "-A"]);
|
|
210
|
-
|
|
237
|
+
// Read after `git init`, so an `includeIf "gitdir:…"` identity for the new repo applies.
|
|
238
|
+
const identityArgs = await gitIdentityFallbackArgs(gitShell);
|
|
239
|
+
// --no-verify: the hooks the install just set up are for the user's commits, not the template as generated.
|
|
240
|
+
await gitShell.runCaptured("git", [...identityArgs, "commit", "--no-verify", "-m", "chore: initial commit from vland"]);
|
|
211
241
|
gitSpin.stop("Initialised git repository");
|
|
212
242
|
} catch (error) {
|
|
213
243
|
gitSpin.stop("Failed to initialise git", 1);
|
|
@@ -9,6 +9,8 @@ export type Placeholders = {
|
|
|
9
9
|
projectName: string;
|
|
10
10
|
author: string;
|
|
11
11
|
year: string;
|
|
12
|
+
packageName: string;
|
|
13
|
+
githubOwner: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
const TEXT_EXTENSIONS = new Set([
|
|
@@ -86,7 +88,9 @@ function applyPlaceholders(content: string, values: Placeholders) {
|
|
|
86
88
|
return content
|
|
87
89
|
.replaceAll("{{projectName}}", values.projectName)
|
|
88
90
|
.replaceAll("{{author}}", values.author)
|
|
89
|
-
.replaceAll("{{year}}", values.year)
|
|
91
|
+
.replaceAll("{{year}}", values.year)
|
|
92
|
+
.replaceAll("{{packageName}}", values.packageName)
|
|
93
|
+
.replaceAll("{{githubOwner}}", values.githubOwner);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
export async function replacePlaceholders(rootDir: string, values: Placeholders) {
|
|
@@ -119,15 +123,21 @@ export type ApplyRootPackageOptions = {
|
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* JSON formatting survive.
|
|
125
|
-
*
|
|
126
|
-
* Naming rules:
|
|
127
|
-
* - `library` private → `@variableland/<projectName>`, `private: true`
|
|
126
|
+
* The root package's name:
|
|
127
|
+
* - `library` private → `@variableland/<projectName>`
|
|
128
128
|
* - `library` public → `@variablelab/<projectName>`
|
|
129
|
-
* - `backend`
|
|
130
|
-
|
|
129
|
+
* - `backend`, `monorepo` → `<projectName>`
|
|
130
|
+
*/
|
|
131
|
+
export function rootPackageName({ template, projectName, visibility }: ApplyRootPackageOptions): string {
|
|
132
|
+
return template === "library" ? `${LIBRARY_SCOPES[visibility ?? "public"]}/${projectName}` : projectName;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Writes the root `package.json`'s `name` ({@link rootPackageName}) and
|
|
137
|
+
* `private` fields based on the chosen template and visibility. Uses
|
|
138
|
+
* `pkg-types` so field ordering and JSON formatting survive. `private: true`
|
|
139
|
+
* goes on a private library and on every backend and monorepo root, which
|
|
140
|
+
* never publishes.
|
|
131
141
|
*/
|
|
132
142
|
export async function applyRootPackage(rootDir: string, options: ApplyRootPackageOptions) {
|
|
133
143
|
const debug = logger.subdebug("apply-root-package");
|
|
@@ -136,18 +146,11 @@ export async function applyRootPackage(rootDir: string, options: ApplyRootPackag
|
|
|
136
146
|
const pkg = await createPkg(rootPath);
|
|
137
147
|
if (!pkg) throw new Error("Could not find package.json");
|
|
138
148
|
|
|
139
|
-
const { template,
|
|
149
|
+
const { template, visibility } = options;
|
|
140
150
|
|
|
141
151
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
pkg.packageJson.name = `${scope}/${projectName}`;
|
|
145
|
-
if (visibility === "private") {
|
|
146
|
-
pkg.packageJson.private = true;
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
// backend + monorepo: bare project name, private root.
|
|
150
|
-
pkg.packageJson.name = projectName;
|
|
152
|
+
pkg.packageJson.name = rootPackageName(options);
|
|
153
|
+
if (template !== "library" || visibility === "private") {
|
|
151
154
|
pkg.packageJson.private = true;
|
|
152
155
|
}
|
|
153
156
|
await pkg.write(pkg.packageJson);
|
package/src/actions/template.ts
CHANGED
|
@@ -25,6 +25,16 @@ export const LIBRARY_SCOPES: Record<Visibility, string> = {
|
|
|
25
25
|
public: "@variablelab",
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The GitHub owner of a project's repository, indexed by visibility: public
|
|
30
|
+
* projects live in the open-source `withvariablelab` org, private ones in
|
|
31
|
+
* `variableland`.
|
|
32
|
+
*/
|
|
33
|
+
export const GITHUB_OWNERS: Record<Visibility, string> = {
|
|
34
|
+
private: "variableland",
|
|
35
|
+
public: "withvariablelab",
|
|
36
|
+
};
|
|
37
|
+
|
|
28
38
|
const GITHUB_SOURCE = "github:withvariablelab/dx";
|
|
29
39
|
const GITHUB_REF = "main";
|
|
30
40
|
|
|
@@ -11,12 +11,21 @@ export function createCompletionCommand() {
|
|
|
11
11
|
return createCommand("completion")
|
|
12
12
|
.summary(`print shell completion script (${TOOL_LABELS.USAGE})`)
|
|
13
13
|
.description(
|
|
14
|
-
`Prints a shell completion script for vland. Add
|
|
14
|
+
`Prints a shell completion script for vland. Add your shell's line to its rc file:
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# bash — ~/.bashrc
|
|
17
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion bash 2>/dev/null)"
|
|
18
|
+
|
|
19
|
+
# zsh — ~/.zshrc
|
|
20
|
+
command -v vland >/dev/null 2>&1 && eval "$(vland completion zsh 2>/dev/null)"
|
|
21
|
+
|
|
22
|
+
# fish — ~/.config/fish/config.fish
|
|
23
|
+
command -v vland >/dev/null 2>&1; and vland completion fish 2>/dev/null | source`,
|
|
19
24
|
)
|
|
25
|
+
.configureHelp({
|
|
26
|
+
// Commander hard-wraps help at the terminal width; a wrapped rc line breaks when pasted.
|
|
27
|
+
helpWidth: Number.POSITIVE_INFINITY,
|
|
28
|
+
})
|
|
20
29
|
.addArgument(new Argument("<shell>", `target shell`).choices(SHELLS))
|
|
21
30
|
.addHelpText(
|
|
22
31
|
"afterAll",
|