@dunx/create-app 0.2.5 → 0.2.7
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/chunk-rnjjb0bq.js +69 -0
- package/dist/chunk-rnjjb0bq.js.map +10 -0
- package/dist/cli.js +6 -67
- package/dist/cli.js.map +4 -5
- package/dist/index.js +7 -64
- package/dist/index.js.map +3 -4
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/scaffold.ts
|
|
3
|
+
import { existsSync, readdirSync } from "fs";
|
|
4
|
+
import { basename, dirname, join, resolve } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var {Glob } = globalThis.Bun;
|
|
7
|
+
var TEMPLATES = Object.freeze(["minimal"]);
|
|
8
|
+
var VERSION_PLACEHOLDER = "__DUNX_VERSION__";
|
|
9
|
+
var RENAMED = Object.freeze({ _gitignore: ".gitignore" });
|
|
10
|
+
var IGNORED_WHEN_EMPTY = new Set([
|
|
11
|
+
".DS_Store",
|
|
12
|
+
".git",
|
|
13
|
+
".gitkeep",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
class ScaffoldError extends Error {
|
|
18
|
+
name = "ScaffoldError";
|
|
19
|
+
}
|
|
20
|
+
var templatesRoot = () => resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
21
|
+
var isValidPackageName = (name) => /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
|
|
22
|
+
var readPackageVersion = async () => {
|
|
23
|
+
const file = Bun.file(join(templatesRoot(), "..", "package.json"));
|
|
24
|
+
const json = await file.json();
|
|
25
|
+
return json.version ?? "0.0.0";
|
|
26
|
+
};
|
|
27
|
+
var scaffold = async (options) => {
|
|
28
|
+
const template = options.template ?? "minimal";
|
|
29
|
+
if (!TEMPLATES.includes(template)) {
|
|
30
|
+
throw new ScaffoldError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}.`);
|
|
31
|
+
}
|
|
32
|
+
const directory = resolve(options.cwd ?? process.cwd(), options.target);
|
|
33
|
+
const name = options.name ?? basename(directory);
|
|
34
|
+
if (!isValidPackageName(name)) {
|
|
35
|
+
throw new ScaffoldError(`"${name}" is not a usable package name. Pass --name to choose one.`);
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(directory) && options.force !== true) {
|
|
38
|
+
const blocking = readdirSync(directory).filter((entry) => !IGNORED_WHEN_EMPTY.has(entry));
|
|
39
|
+
if (blocking.length > 0) {
|
|
40
|
+
const shown = blocking.sort().slice(0, 3).join(", ");
|
|
41
|
+
const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : "";
|
|
42
|
+
throw new ScaffoldError(`${directory} is not empty (${shown}${rest}). ` + `Pass --force to write into it anyway.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const source = join(templatesRoot(), template);
|
|
46
|
+
if (!existsSync(source)) {
|
|
47
|
+
throw new ScaffoldError(`Template "${template}" is missing from ${source}.`);
|
|
48
|
+
}
|
|
49
|
+
const version = options.version ?? `^${await readPackageVersion()}`;
|
|
50
|
+
const written = [];
|
|
51
|
+
for await (const relative of new Glob("**/*").scan({
|
|
52
|
+
cwd: source,
|
|
53
|
+
dot: true,
|
|
54
|
+
onlyFiles: true
|
|
55
|
+
})) {
|
|
56
|
+
const base = relative.split("/").at(-1) ?? relative;
|
|
57
|
+
const renamed = RENAMED[base];
|
|
58
|
+
const target = renamed === undefined ? relative : join(dirname(relative), renamed);
|
|
59
|
+
const contents = await Bun.file(join(source, relative)).text();
|
|
60
|
+
await Bun.write(join(directory, target), contents.replaceAll(VERSION_PLACEHOLDER, version).replaceAll("__DUNX_APP_NAME__", name));
|
|
61
|
+
written.push(target);
|
|
62
|
+
}
|
|
63
|
+
return { directory, name, template, files: written.sort() };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { TEMPLATES, VERSION_PLACEHOLDER, ScaffoldError, scaffold };
|
|
67
|
+
|
|
68
|
+
//# debugId=C00AF4D7141E1B0564756E2164756E21
|
|
69
|
+
//# sourceMappingURL=chunk-rnjjb0bq.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/scaffold.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/** npm renames a published `.gitignore` to `.npmignore`, so it ships prefixed. */\nconst RENAMED = Object.freeze({ _gitignore: '.gitignore' });\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName;\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: source,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target =\n renamed === undefined ? relative : join(dirname(relative), renamed);\n\n const contents = await Bun.file(join(source, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(\n join(directory, target),\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name),\n );\n written.push(target);\n }\n\n return { directory, name, template, files: written.sort() };\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;AAAA;AACA;AACA;AACA;AAGO,IAAM,YAAY,OAAO,OAAO,CAAC,SAAS,CAAU;AASpD,IAAM,sBAAsB;AAGnC,IAAM,UAAU,OAAO,OAAO,EAAE,YAAY,aAAa,CAAC;AAiB1D,IAAM,qBAA0C,IAAI,IAAI;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAAA;AAsBM,MAAM,sBAAsB,MAAM;AAAA,EACrB,OAAO;AAC3B;AAaA,IAAM,gBAAgB,MACpB,QAAQ,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,MAAM,WAAW;AAOpE,IAAM,qBAAqB,CAAC,SAC1B,6DAA6D,KAAK,IAAI;AAExE,IAAM,qBAAqB,YAA6B;AAAA,EACtD,MAAM,OAAO,IAAI,KAAK,KAAK,cAAc,GAAG,MAAM,cAAc,CAAC;AAAA,EACjE,MAAM,OAAQ,MAAM,KAAK,KAAK;AAAA,EAC9B,OAAO,KAAK,WAAW;AAAA;AAGlB,IAAM,WAAW,OACtB,YAC4B;AAAA,EAC5B,MAAM,WAAW,QAAQ,YAAY;AAAA,EACrC,IAAI,CAAC,UAAU,SAAS,QAAQ,GAAG;AAAA,IACjC,MAAM,IAAI,cACR,qBAAqB,yBAAyB,UAAU,KAAK,IAAI,IACnE;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,QAAQ,QAAQ,OAAO,QAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,EACtE,MAAM,OAAO,QAAQ,QAAQ,SAAS,SAAS;AAAA,EAE/C,IAAI,CAAC,mBAAmB,IAAI,GAAG;AAAA,IAC7B,MAAM,IAAI,cACR,IAAI,gEACN;AAAA,EACF;AAAA,EAEA,IAAI,WAAW,SAAS,KAAK,QAAQ,UAAU,MAAM;AAAA,IACnD,MAAM,WAAW,YAAY,SAAS,EAAE,OACtC,CAAC,UAAU,CAAC,mBAAmB,IAAI,KAAK,CAC1C;AAAA,IACA,IAAI,SAAS,SAAS,GAAG;AAAA,MAGvB,MAAM,QAAQ,SAAS,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAAA,MACnD,MAAM,OAAO,SAAS,SAAS,IAAI,MAAM,SAAS,SAAS,WAAW;AAAA,MACtE,MAAM,IAAI,cACR,GAAG,2BAA2B,QAAQ,YACpC,uCACJ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAAK,cAAc,GAAG,QAAQ;AAAA,EAC7C,IAAI,CAAC,WAAW,MAAM,GAAG;AAAA,IACvB,MAAM,IAAI,cACR,aAAa,6BAA6B,SAC5C;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,QAAQ,WAAW,IAAI,MAAM,mBAAmB;AAAA,EAChE,MAAM,UAAoB,CAAC;AAAA,EAI3B,iBAAiB,YAAY,IAAI,KAAK,MAAM,EAAE,KAAK;AAAA,IACjD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,WAAW;AAAA,EACb,CAAC,GAAG;AAAA,IACF,MAAM,OAAO,SAAS,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK;AAAA,IAC3C,MAAM,UAAW,QAA+C;AAAA,IAChE,MAAM,SACJ,YAAY,YAAY,WAAW,KAAK,QAAQ,QAAQ,GAAG,OAAO;AAAA,IAEpE,MAAM,WAAW,MAAM,IAAI,KAAK,KAAK,QAAQ,QAAQ,CAAC,EAAE,KAAK;AAAA,IAE7D,MAAM,IAAI,MACR,KAAK,WAAW,MAAM,GACtB,SACG,WAAW,qBAAqB,OAAO,EACvC,WAAW,qBAAqB,IAAI,CACzC;AAAA,IACA,QAAQ,KAAK,MAAM;AAAA,EACrB;AAAA,EAEA,OAAO,EAAE,WAAW,MAAM,UAAU,OAAO,QAAQ,KAAK,EAAE;AAAA;",
|
|
8
|
+
"debugId": "C00AF4D7141E1B0564756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,75 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
import {
|
|
4
|
+
ScaffoldError,
|
|
5
|
+
TEMPLATES,
|
|
6
|
+
scaffold
|
|
7
|
+
} from "./chunk-rnjjb0bq.js";
|
|
3
8
|
|
|
4
9
|
// src/cli.ts
|
|
5
10
|
import { parseArgs } from "util";
|
|
6
11
|
import { relative } from "path";
|
|
7
|
-
|
|
8
|
-
// src/scaffold.ts
|
|
9
|
-
import { existsSync, readdirSync } from "fs";
|
|
10
|
-
import { basename, dirname, join, resolve } from "path";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
var {Glob } = globalThis.Bun;
|
|
13
|
-
var TEMPLATES = Object.freeze(["minimal"]);
|
|
14
|
-
var VERSION_PLACEHOLDER = "__DUNX_VERSION__";
|
|
15
|
-
var RENAMED = Object.freeze({ _gitignore: ".gitignore" });
|
|
16
|
-
var IGNORED_WHEN_EMPTY = new Set([
|
|
17
|
-
".DS_Store",
|
|
18
|
-
".git",
|
|
19
|
-
".gitkeep",
|
|
20
|
-
"LICENSE"
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
class ScaffoldError extends Error {
|
|
24
|
-
name = "ScaffoldError";
|
|
25
|
-
}
|
|
26
|
-
var templatesRoot = () => resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
27
|
-
var isValidPackageName = (name) => /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
|
|
28
|
-
var readPackageVersion = async () => {
|
|
29
|
-
const file = Bun.file(join(templatesRoot(), "..", "package.json"));
|
|
30
|
-
const json = await file.json();
|
|
31
|
-
return json.version ?? "0.0.0";
|
|
32
|
-
};
|
|
33
|
-
var scaffold = async (options) => {
|
|
34
|
-
const template = options.template ?? "minimal";
|
|
35
|
-
if (!TEMPLATES.includes(template)) {
|
|
36
|
-
throw new ScaffoldError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}.`);
|
|
37
|
-
}
|
|
38
|
-
const directory = resolve(options.cwd ?? process.cwd(), options.target);
|
|
39
|
-
const name = options.name ?? basename(directory);
|
|
40
|
-
if (!isValidPackageName(name)) {
|
|
41
|
-
throw new ScaffoldError(`"${name}" is not a usable package name. Pass --name to choose one.`);
|
|
42
|
-
}
|
|
43
|
-
if (existsSync(directory) && options.force !== true) {
|
|
44
|
-
const blocking = readdirSync(directory).filter((entry) => !IGNORED_WHEN_EMPTY.has(entry));
|
|
45
|
-
if (blocking.length > 0) {
|
|
46
|
-
const shown = blocking.sort().slice(0, 3).join(", ");
|
|
47
|
-
const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : "";
|
|
48
|
-
throw new ScaffoldError(`${directory} is not empty (${shown}${rest}). ` + `Pass --force to write into it anyway.`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const source = join(templatesRoot(), template);
|
|
52
|
-
if (!existsSync(source)) {
|
|
53
|
-
throw new ScaffoldError(`Template "${template}" is missing from ${source}.`);
|
|
54
|
-
}
|
|
55
|
-
const version = options.version ?? `^${await readPackageVersion()}`;
|
|
56
|
-
const written = [];
|
|
57
|
-
for await (const relative of new Glob("**/*").scan({
|
|
58
|
-
cwd: source,
|
|
59
|
-
dot: true,
|
|
60
|
-
onlyFiles: true
|
|
61
|
-
})) {
|
|
62
|
-
const base = relative.split("/").at(-1) ?? relative;
|
|
63
|
-
const renamed = RENAMED[base];
|
|
64
|
-
const target = renamed === undefined ? relative : join(dirname(relative), renamed);
|
|
65
|
-
const contents = await Bun.file(join(source, relative)).text();
|
|
66
|
-
await Bun.write(join(directory, target), contents.replaceAll(VERSION_PLACEHOLDER, version).replaceAll("__DUNX_APP_NAME__", name));
|
|
67
|
-
written.push(target);
|
|
68
|
-
}
|
|
69
|
-
return { directory, name, template, files: written.sort() };
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// src/cli.ts
|
|
73
12
|
var USAGE = `Scaffold a new dunx application.
|
|
74
13
|
|
|
75
14
|
bunx @dunx/create-app <directory> [options]
|
|
@@ -128,5 +67,5 @@ try {
|
|
|
128
67
|
throw error;
|
|
129
68
|
}
|
|
130
69
|
|
|
131
|
-
//# debugId=
|
|
70
|
+
//# debugId=8A4688E6CED7F9A864756E2164756E21
|
|
132
71
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/cli.ts"
|
|
3
|
+
"sources": ["../src/cli.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"#!/usr/bin/env bun\nimport { parseArgs } from 'node:util';\nimport { relative } from 'node:path';\nimport { scaffold, ScaffoldError, TEMPLATES } from './scaffold.js';\nimport type { TemplateName } from './scaffold.js';\n\nconst USAGE = `Scaffold a new dunx application.\n\n bunx @dunx/create-app <directory> [options]\n\nOptions:\n --name <name> package name for the app (default: the directory name)\n --template <name> ${TEMPLATES.join(' | ')} (default: minimal)\n --force write into a directory that already has files in it\n --yes, -y accepted and ignored; nothing here ever prompts\n --help print this\n`;\n\n// A declaration, not a `const` arrow: control-flow analysis only narrows past a\n// never-returning call when the callee is declared this way, so `target` stays\n// `string | undefined` below if this is an arrow.\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nconst { values, positionals } = parseArgs({\n args: Bun.argv.slice(2),\n allowPositionals: true,\n options: {\n name: { type: 'string' },\n template: { type: 'string' },\n force: { type: 'boolean', default: false },\n // Declared and never read. The scaffolder is fully non-interactive, so there\n // is nothing for `--yes` to confirm - but it is what a hand reaches for out of\n // habit, and `parseArgs` answers an undeclared flag with a `TypeError`.\n yes: { type: 'boolean', default: false, short: 'y' },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n});\n\nif (values.help === true) {\n console.log(USAGE);\n process.exit(0);\n}\n\nconst target = positionals[0];\nif (target === undefined) {\n fail(`Missing the target directory.\\n\\n${USAGE}`);\n}\n\ntry {\n const result = await scaffold({\n target,\n ...(values.name === undefined ? {} : { name: values.name }),\n ...(values.template === undefined\n ? {}\n : { template: values.template as TemplateName }),\n force: values.force === true,\n });\n\n // Empty when the target resolved to the directory the process is already in, in\n // which case `in ./` and `cd .` are both noise to a reader standing there.\n const where = relative(process.cwd(), result.directory);\n console.log(\n where === ''\n ? `Created ${result.name} here`\n : `Created ${result.name} in ${where}/`,\n );\n console.log(\n ` ${result.files.length} files from the ${result.template} template\\n`,\n );\n console.log('Next:');\n if (where !== '') console.log(` cd ${where}`);\n console.log(' bun install');\n console.log(' bun run start');\n} catch (error) {\n if (error instanceof ScaffoldError) fail(error.message);\n throw error;\n}\n"
|
|
6
|
-
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/** npm renames a published `.gitignore` to `.npmignore`, so it ships prefixed. */\nconst RENAMED = Object.freeze({ _gitignore: '.gitignore' });\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName;\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: source,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target =\n renamed === undefined ? relative : join(dirname(relative), renamed);\n\n const contents = await Bun.file(join(source, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(\n join(directory, target),\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name),\n );\n written.push(target);\n }\n\n return { directory, name, template, files: written.sort() };\n};\n"
|
|
5
|
+
"#!/usr/bin/env bun\nimport { parseArgs } from 'node:util';\nimport { relative } from 'node:path';\nimport { scaffold, ScaffoldError, TEMPLATES } from './scaffold.js';\nimport type { TemplateName } from './scaffold.js';\n\nconst USAGE = `Scaffold a new dunx application.\n\n bunx @dunx/create-app <directory> [options]\n\nOptions:\n --name <name> package name for the app (default: the directory name)\n --template <name> ${TEMPLATES.join(' | ')} (default: minimal)\n --force write into a directory that already has files in it\n --yes, -y accepted and ignored; nothing here ever prompts\n --help print this\n`;\n\n// A declaration, not a `const` arrow: control-flow analysis only narrows past a\n// never-returning call when the callee is declared this way, so `target` stays\n// `string | undefined` below if this is an arrow.\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nconst { values, positionals } = parseArgs({\n args: Bun.argv.slice(2),\n allowPositionals: true,\n options: {\n name: { type: 'string' },\n template: { type: 'string' },\n force: { type: 'boolean', default: false },\n // Declared and never read. The scaffolder is fully non-interactive, so there\n // is nothing for `--yes` to confirm - but it is what a hand reaches for out of\n // habit, and `parseArgs` answers an undeclared flag with a `TypeError`.\n yes: { type: 'boolean', default: false, short: 'y' },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n});\n\nif (values.help === true) {\n console.log(USAGE);\n process.exit(0);\n}\n\nconst target = positionals[0];\nif (target === undefined) {\n fail(`Missing the target directory.\\n\\n${USAGE}`);\n}\n\ntry {\n const result = await scaffold({\n target,\n ...(values.name === undefined ? {} : { name: values.name }),\n ...(values.template === undefined\n ? {}\n : { template: values.template as TemplateName }),\n force: values.force === true,\n });\n\n // Empty when the target resolved to the directory the process is already in, in\n // which case `in ./` and `cd .` are both noise to a reader standing there.\n const where = relative(process.cwd(), result.directory);\n console.log(\n where === ''\n ? `Created ${result.name} here`\n : `Created ${result.name} in ${where}/`,\n );\n console.log(\n ` ${result.files.length} files from the ${result.template} template\\n`,\n );\n console.log('Next:');\n if (where !== '') console.log(` cd ${where}`);\n console.log(' bun install');\n console.log(' bun run start');\n} catch (error) {\n if (error instanceof ScaffoldError) fail(error.message);\n throw error;\n}\n"
|
|
7
6
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;AACA;AACA;AAIA,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAMU,UAAU,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA;AAS5C,SAAS,IAAI,CAAC,SAAwB;AAAA,EACpC,QAAQ,MAAM,OAAO;AAAA,EACrB,QAAQ,KAAK,CAAC;AAAA;AAGhB,MAAQ,QAAQ,gBAAgB,UAAU;AAAA,EACxC,MAAM,IAAI,KAAK,MAAM,CAAC;AAAA,EACtB,kBAAkB;AAAA,EAClB,SAAS;AAAA,IACP,MAAM,EAAE,MAAM,SAAS;AAAA,IACvB,UAAU,EAAE,MAAM,SAAS;AAAA,IAC3B,OAAO,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,IAIzC,KAAK,EAAE,MAAM,WAAW,SAAS,OAAO,OAAO,IAAI;AAAA,IACnD,MAAM,EAAE,MAAM,WAAW,SAAS,OAAO,OAAO,IAAI;AAAA,EACtD;AACF,CAAC;AAED,IAAI,OAAO,SAAS,MAAM;AAAA,EACxB,QAAQ,IAAI,KAAK;AAAA,EACjB,QAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,SAAS,YAAY;AAC3B,IAAI,WAAW,WAAW;AAAA,EACxB,KAAK;AAAA;AAAA,EAAoC,OAAO;AAClD;AAEA,IAAI;AAAA,EACF,MAAM,SAAS,MAAM,SAAS;AAAA,IAC5B;AAAA,OACI,OAAO,SAAS,YAAY,CAAC,IAAI,EAAE,MAAM,OAAO,KAAK;AAAA,OACrD,OAAO,aAAa,YACpB,CAAC,IACD,EAAE,UAAU,OAAO,SAAyB;AAAA,IAChD,OAAO,OAAO,UAAU;AAAA,EAC1B,CAAC;AAAA,EAID,MAAM,QAAQ,SAAS,QAAQ,IAAI,GAAG,OAAO,SAAS;AAAA,EACtD,QAAQ,IACN,UAAU,KACN,WAAW,OAAO,cAClB,WAAW,OAAO,WAAW,QACnC;AAAA,EACA,QAAQ,IACN,KAAK,OAAO,MAAM,yBAAyB,OAAO;AAAA,CACpD;AAAA,EACA,QAAQ,IAAI,OAAO;AAAA,EACnB,IAAI,UAAU;AAAA,IAAI,QAAQ,IAAI,QAAQ,OAAO;AAAA,EAC7C,QAAQ,IAAI,eAAe;AAAA,EAC3B,QAAQ,IAAI,iBAAiB;AAAA,EAC7B,OAAO,OAAO;AAAA,EACd,IAAI,iBAAiB;AAAA,IAAe,KAAK,MAAM,OAAO;AAAA,EACtD,MAAM;AAAA;",
|
|
8
|
+
"debugId": "8A4688E6CED7F9A864756E2164756E21",
|
|
10
9
|
"names": []
|
|
11
10
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,67 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var VERSION_PLACEHOLDER = "__DUNX_VERSION__";
|
|
9
|
-
var RENAMED = Object.freeze({ _gitignore: ".gitignore" });
|
|
10
|
-
var IGNORED_WHEN_EMPTY = new Set([
|
|
11
|
-
".DS_Store",
|
|
12
|
-
".git",
|
|
13
|
-
".gitkeep",
|
|
14
|
-
"LICENSE"
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
class ScaffoldError extends Error {
|
|
18
|
-
name = "ScaffoldError";
|
|
19
|
-
}
|
|
20
|
-
var templatesRoot = () => resolve(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
21
|
-
var isValidPackageName = (name) => /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
|
|
22
|
-
var readPackageVersion = async () => {
|
|
23
|
-
const file = Bun.file(join(templatesRoot(), "..", "package.json"));
|
|
24
|
-
const json = await file.json();
|
|
25
|
-
return json.version ?? "0.0.0";
|
|
26
|
-
};
|
|
27
|
-
var scaffold = async (options) => {
|
|
28
|
-
const template = options.template ?? "minimal";
|
|
29
|
-
if (!TEMPLATES.includes(template)) {
|
|
30
|
-
throw new ScaffoldError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}.`);
|
|
31
|
-
}
|
|
32
|
-
const directory = resolve(options.cwd ?? process.cwd(), options.target);
|
|
33
|
-
const name = options.name ?? basename(directory);
|
|
34
|
-
if (!isValidPackageName(name)) {
|
|
35
|
-
throw new ScaffoldError(`"${name}" is not a usable package name. Pass --name to choose one.`);
|
|
36
|
-
}
|
|
37
|
-
if (existsSync(directory) && options.force !== true) {
|
|
38
|
-
const blocking = readdirSync(directory).filter((entry) => !IGNORED_WHEN_EMPTY.has(entry));
|
|
39
|
-
if (blocking.length > 0) {
|
|
40
|
-
const shown = blocking.sort().slice(0, 3).join(", ");
|
|
41
|
-
const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : "";
|
|
42
|
-
throw new ScaffoldError(`${directory} is not empty (${shown}${rest}). ` + `Pass --force to write into it anyway.`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const source = join(templatesRoot(), template);
|
|
46
|
-
if (!existsSync(source)) {
|
|
47
|
-
throw new ScaffoldError(`Template "${template}" is missing from ${source}.`);
|
|
48
|
-
}
|
|
49
|
-
const version = options.version ?? `^${await readPackageVersion()}`;
|
|
50
|
-
const written = [];
|
|
51
|
-
for await (const relative of new Glob("**/*").scan({
|
|
52
|
-
cwd: source,
|
|
53
|
-
dot: true,
|
|
54
|
-
onlyFiles: true
|
|
55
|
-
})) {
|
|
56
|
-
const base = relative.split("/").at(-1) ?? relative;
|
|
57
|
-
const renamed = RENAMED[base];
|
|
58
|
-
const target = renamed === undefined ? relative : join(dirname(relative), renamed);
|
|
59
|
-
const contents = await Bun.file(join(source, relative)).text();
|
|
60
|
-
await Bun.write(join(directory, target), contents.replaceAll(VERSION_PLACEHOLDER, version).replaceAll("__DUNX_APP_NAME__", name));
|
|
61
|
-
written.push(target);
|
|
62
|
-
}
|
|
63
|
-
return { directory, name, template, files: written.sort() };
|
|
64
|
-
};
|
|
2
|
+
import {
|
|
3
|
+
ScaffoldError,
|
|
4
|
+
TEMPLATES,
|
|
5
|
+
VERSION_PLACEHOLDER,
|
|
6
|
+
scaffold
|
|
7
|
+
} from "./chunk-rnjjb0bq.js";
|
|
65
8
|
export {
|
|
66
9
|
scaffold,
|
|
67
10
|
VERSION_PLACEHOLDER,
|
|
@@ -69,5 +12,5 @@ export {
|
|
|
69
12
|
ScaffoldError
|
|
70
13
|
};
|
|
71
14
|
|
|
72
|
-
//# debugId=
|
|
15
|
+
//# debugId=83B6099C1AC65B2C64756E2164756E21
|
|
73
16
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [
|
|
3
|
+
"sources": [],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/** npm renames a published `.gitignore` to `.npmignore`, so it ships prefixed. */\nconst RENAMED = Object.freeze({ _gitignore: '.gitignore' });\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName;\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: source,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target =\n renamed === undefined ? relative : join(dirname(relative), renamed);\n\n const contents = await Bun.file(join(source, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(\n join(directory, target),\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name),\n );\n written.push(target);\n }\n\n return { directory, name, template, files: written.sort() };\n};\n"
|
|
6
5
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
6
|
+
"mappings": "",
|
|
7
|
+
"debugId": "83B6099C1AC65B2C64756E2164756E21",
|
|
9
8
|
"names": []
|
|
10
9
|
}
|