create-daloy 0.41.0 → 0.43.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/bin/create-daloy.mjs +123 -5
- package/package.json +1 -1
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/bun-basic/package.json +1 -1
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/deno-basic/_Dockerfile +21 -9
- package/templates/deno-basic/deno.json +5 -5
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel/package.json +1 -1
package/bin/create-daloy.mjs
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Zero runtime dependencies; uses only Node built-ins.
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { existsSync, openSync } from "node:fs";
|
|
7
|
-
import { mkdir, readdir, readFile, writeFile, copyFile, rm, stat, chmod } from "node:fs/promises";
|
|
6
|
+
import { existsSync, openSync, constants as FS_CONSTANTS } from "node:fs";
|
|
7
|
+
import { mkdir, readdir, readFile, writeFile, copyFile, rm, stat, chmod, access } from "node:fs/promises";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { stdin as input, stdout as output } from "node:process";
|
|
10
10
|
import { ReadStream as TtyReadStream } from "node:tty";
|
|
@@ -466,6 +466,100 @@ function detectPackageManager() {
|
|
|
466
466
|
return "pnpm";
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
// ----------------------------------------------------------------------------
|
|
470
|
+
// Tooling presence checks.
|
|
471
|
+
//
|
|
472
|
+
// A scaffolded project is only useful if the runtime and package manager its
|
|
473
|
+
// "Next steps" reference are actually installed. When the user picks a path
|
|
474
|
+
// whose CLI is missing from PATH (e.g. `bun-basic` on a machine without Bun, or
|
|
475
|
+
// pnpm on a fresh box), we surface the official install link instead of letting
|
|
476
|
+
// `pnpm run dev` or the dependency install fail with a cryptic spawn error.
|
|
477
|
+
// ----------------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
// Official install guides for every runtime and package manager the scaffolder
|
|
480
|
+
// can target. Keyed by the bare executable name we probe for on PATH.
|
|
481
|
+
const TOOL_INSTALL_GUIDES = {
|
|
482
|
+
node: { label: "Node.js", url: "https://nodejs.org" },
|
|
483
|
+
npm: { label: "npm", url: "https://nodejs.org" },
|
|
484
|
+
pnpm: { label: "pnpm", url: "https://pnpm.io/installation" },
|
|
485
|
+
yarn: { label: "Yarn", url: "https://yarnpkg.com/getting-started/install" },
|
|
486
|
+
bun: { label: "Bun", url: "https://bun.sh" },
|
|
487
|
+
deno: { label: "Deno", url: "https://docs.deno.com/runtime/getting_started/installation/" },
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Compute the CLIs a freshly-scaffolded project needs to run, given the chosen
|
|
492
|
+
* template (which fixes the runtime) and package manager. Runtime-only
|
|
493
|
+
* templates such as `deno-basic` drive the runtime directly, so they need the
|
|
494
|
+
* runtime but no npm-style package manager.
|
|
495
|
+
*
|
|
496
|
+
* @param {object} selection
|
|
497
|
+
* @param {string} selection.template - resolved template value, e.g. `node-basic`.
|
|
498
|
+
* @param {string} [selection.packageManager] - chosen package manager (pnpm/npm/yarn/bun).
|
|
499
|
+
* @param {boolean} [selection.skipPackageManager] - true for runtime-only templates (deno).
|
|
500
|
+
* @returns {string[]} bare executable names the scaffold depends on, deduped.
|
|
501
|
+
*/
|
|
502
|
+
function requiredTools({ template, packageManager, skipPackageManager }) {
|
|
503
|
+
const tools = new Set();
|
|
504
|
+
if (template === "bun-basic") tools.add("bun");
|
|
505
|
+
else if (template === "deno-basic") tools.add("deno");
|
|
506
|
+
else tools.add("node");
|
|
507
|
+
if (!skipPackageManager && packageManager) tools.add(packageManager);
|
|
508
|
+
return [...tools];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Resolve whether an executable is reachable on PATH **without executing it**.
|
|
513
|
+
* We scan each PATH entry for a matching file and check it is executable
|
|
514
|
+
* (POSIX) or simply present (Windows, where `PATHEXT` governs runnability).
|
|
515
|
+
* Avoiding a spawn keeps the check fast, side-effect-free, and immune to a
|
|
516
|
+
* misbehaving binary hanging the scaffolder.
|
|
517
|
+
*
|
|
518
|
+
* @param {string} binary - bare command name, e.g. `pnpm` or `node`.
|
|
519
|
+
* @param {NodeJS.ProcessEnv} [env=process.env] - environment to read PATH from.
|
|
520
|
+
* @returns {Promise<boolean>} true when a runnable match is found on PATH.
|
|
521
|
+
*/
|
|
522
|
+
async function isToolInstalled(binary, env = process.env) {
|
|
523
|
+
const pathValue = env.PATH ?? env.Path ?? "";
|
|
524
|
+
if (!pathValue) return false;
|
|
525
|
+
const dirs = pathValue.split(path.delimiter).filter(Boolean);
|
|
526
|
+
const isWindows = process.platform === "win32";
|
|
527
|
+
const extensions = isWindows
|
|
528
|
+
? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => ext.trim()).filter(Boolean)
|
|
529
|
+
: [""];
|
|
530
|
+
const mode = isWindows ? FS_CONSTANTS.F_OK : FS_CONSTANTS.X_OK;
|
|
531
|
+
for (const dir of dirs) {
|
|
532
|
+
for (const ext of extensions) {
|
|
533
|
+
try {
|
|
534
|
+
await access(path.join(dir, `${binary}${ext}`), mode);
|
|
535
|
+
return true;
|
|
536
|
+
} catch {
|
|
537
|
+
// Not in this PATH entry (or not executable); keep scanning.
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Filter `tools` down to those that are not installed, pairing each with its
|
|
546
|
+
* official install guide. The `isInstalled` predicate is injected so the
|
|
547
|
+
* resolution stays unit-testable without touching the real PATH.
|
|
548
|
+
*
|
|
549
|
+
* @param {string[]} tools - bare executable names to probe (see {@link requiredTools}).
|
|
550
|
+
* @param {(binary: string) => boolean | Promise<boolean>} isInstalled - presence predicate.
|
|
551
|
+
* @returns {Promise<Array<{ tool: string, label: string, url: string }>>} missing tools + guides.
|
|
552
|
+
*/
|
|
553
|
+
async function missingToolGuides(tools, isInstalled) {
|
|
554
|
+
const missing = [];
|
|
555
|
+
for (const tool of tools) {
|
|
556
|
+
const guide = TOOL_INSTALL_GUIDES[tool];
|
|
557
|
+
if (!guide) continue;
|
|
558
|
+
if (!(await isInstalled(tool))) missing.push({ tool, ...guide });
|
|
559
|
+
}
|
|
560
|
+
return missing;
|
|
561
|
+
}
|
|
562
|
+
|
|
469
563
|
const VALID_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
470
564
|
// Match the GitHub CODEOWNERS owner grammar: a personal handle (@user), an
|
|
471
565
|
// organization team (@org/team), or an email address. Anything else is
|
|
@@ -1567,7 +1661,7 @@ function createSpinner(initialMessage) {
|
|
|
1567
1661
|
};
|
|
1568
1662
|
}
|
|
1569
1663
|
|
|
1570
|
-
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi, withDeploy }) {
|
|
1664
|
+
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi, withDeploy, missingTools = [] }) {
|
|
1571
1665
|
const templateMeta = TEMPLATE_OPTIONS.find((option) => option.value === template);
|
|
1572
1666
|
const templateLabel = templateMeta ? `${templateMeta.title} ${color(COLORS.dim, `(${template})`)}` : template;
|
|
1573
1667
|
const summaryLines = [
|
|
@@ -1601,6 +1695,19 @@ function printSummary({ projectName, template, packageManager, installDeps, skip
|
|
|
1601
1695
|
console.log(` ${arrow} ${color(COLORS.cyan, `${packageManager} run dev`)}`);
|
|
1602
1696
|
}
|
|
1603
1697
|
|
|
1698
|
+
// Surface install links for any runtime/package manager the commands above
|
|
1699
|
+
// rely on that isn't on PATH — saves a confusing "command not found" on the
|
|
1700
|
+
// user's very first step.
|
|
1701
|
+
if (missingTools.length > 0) {
|
|
1702
|
+
const labelWidth = Math.max(...missingTools.map((tool) => tool.label.length));
|
|
1703
|
+
console.log("");
|
|
1704
|
+
console.log(`${color(COLORS.yellow, SYMBOLS.warn)} ${color(COLORS.bold, "Install these to run the commands above")}`);
|
|
1705
|
+
for (const { label, url } of missingTools) {
|
|
1706
|
+
const name = color(COLORS.bold, label.padEnd(labelWidth));
|
|
1707
|
+
console.log(` ${color(COLORS.yellow, SYMBOLS.pointer)} ${name} ${color(COLORS.cyan + COLORS.underline, url)}`);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1604
1711
|
if (!installDeps && !skipPackageManager && packageManager === "pnpm") {
|
|
1605
1712
|
console.log("");
|
|
1606
1713
|
console.log(`${color(COLORS.bold, "Heads-up before \`pnpm install\`")}`);
|
|
@@ -1840,6 +1947,17 @@ async function main() {
|
|
|
1840
1947
|
}
|
|
1841
1948
|
}
|
|
1842
1949
|
|
|
1950
|
+
// Probe PATH for the runtime + package manager this scaffold needs so we
|
|
1951
|
+
// can print official install links (and avoid spawning a doomed install).
|
|
1952
|
+
const missingTools = await missingToolGuides(
|
|
1953
|
+
requiredTools({ template, packageManager, skipPackageManager }),
|
|
1954
|
+
isToolInstalled,
|
|
1955
|
+
);
|
|
1956
|
+
if (installDeps && missingTools.some((tool) => tool.tool === packageManager)) {
|
|
1957
|
+
logWarn(`${packageManager} is not installed; skipping dependency install. Install it from ${TOOL_INSTALL_GUIDES[packageManager].url}`);
|
|
1958
|
+
installDeps = false;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1843
1961
|
if (installDeps) {
|
|
1844
1962
|
const spinner = createSpinner(`Installing dependencies with ${color(COLORS.cyan, packageManager)}\u2026`);
|
|
1845
1963
|
spinner.start();
|
|
@@ -1857,7 +1975,7 @@ async function main() {
|
|
|
1857
1975
|
}
|
|
1858
1976
|
}
|
|
1859
1977
|
|
|
1860
|
-
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi, withDeploy });
|
|
1978
|
+
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi, withDeploy, missingTools });
|
|
1861
1979
|
} catch (err) {
|
|
1862
1980
|
rl?.close();
|
|
1863
1981
|
if (err && err.message === "Cancelled") {
|
|
@@ -1874,4 +1992,4 @@ if (process.env.DALOY_TEST_IMPORT !== "1") {
|
|
|
1874
1992
|
await main();
|
|
1875
1993
|
}
|
|
1876
1994
|
|
|
1877
|
-
export { choiceInputMode };
|
|
1995
|
+
export { choiceInputMode, requiredTools, missingToolGuides, isToolInstalled, TOOL_INSTALL_GUIDES };
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:c421c888-ec1b-5fd0-9d15-8ff8de111b6c",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
7
|
+
"timestamp": "2026-06-20T12:10:10.496Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.43.0"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"authors": [],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "library",
|
|
18
|
-
"bom-ref": "pkg:npm/create-daloy@0.
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@0.43.0",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.43.0",
|
|
21
21
|
"description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
|
|
22
|
-
"purl": "pkg:npm/create-daloy@0.
|
|
22
|
+
"purl": "pkg:npm/create-daloy@0.43.0",
|
|
23
23
|
"licenses": [
|
|
24
24
|
{
|
|
25
25
|
"license": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"swid": {
|
|
45
|
-
"tagId": "swidtag-create-daloy-0.
|
|
45
|
+
"tagId": "swidtag-create-daloy-0.43.0",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "0.
|
|
47
|
+
"version": "0.43.0",
|
|
48
48
|
"tagVersion": 0,
|
|
49
49
|
"patch": false
|
|
50
50
|
}
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"components": [],
|
|
54
54
|
"dependencies": [
|
|
55
55
|
{
|
|
56
|
-
"ref": "pkg:npm/create-daloy@0.
|
|
56
|
+
"ref": "pkg:npm/create-daloy@0.43.0",
|
|
57
57
|
"dependsOn": []
|
|
58
58
|
}
|
|
59
59
|
]
|
package/sbom.spdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "create-daloy-0.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.
|
|
5
|
+
"name": "create-daloy-0.43.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.43.0-c421c888-ec1b-5fd0-9d15-8ff8de111b6c",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-06-
|
|
8
|
+
"created": "2026-06-20T12:10:10.496Z",
|
|
9
9
|
"creators": [
|
|
10
10
|
"Tool: daloy-generate-sbom",
|
|
11
11
|
"Organization: DaloyJS"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"SPDXID": "SPDXRef-Package-create-daloy",
|
|
18
18
|
"name": "create-daloy",
|
|
19
|
-
"versionInfo": "0.
|
|
19
|
+
"versionInfo": "0.43.0",
|
|
20
20
|
"downloadLocation": "https://github.com/daloyjs/daloy",
|
|
21
21
|
"filesAnalyzed": false,
|
|
22
22
|
"licenseConcluded": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
{
|
|
28
28
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
29
29
|
"referenceType": "purl",
|
|
30
|
-
"referenceLocator": "pkg:npm/create-daloy@0.
|
|
30
|
+
"referenceLocator": "pkg:npm/create-daloy@0.43.0"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
|
@@ -5,15 +5,19 @@
|
|
|
5
5
|
# - Non-root runtime user (`deno`, uid 1993 — created by the official
|
|
6
6
|
# `denoland/deno` image).
|
|
7
7
|
# - **Deno's capability-based permission model is the primary
|
|
8
|
-
# defense.** The runtime CMD passes
|
|
9
|
-
# `--allow-env
|
|
10
|
-
# `--allow-
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
8
|
+
# defense.** The runtime CMD passes a scoped `--allow-net`, a
|
|
9
|
+
# `--allow-env` allowlist of only the variables this app reads, and a
|
|
10
|
+
# scoped `--allow-read` — no `--allow-write`, no `--allow-run`, no
|
|
11
|
+
# `--allow-ffi`, no `--allow-sys`, no `--allow-all`. Network access is
|
|
12
|
+
# constrained to the server port and the env allowlist names each
|
|
13
|
+
# variable explicitly, so a compromised dependency cannot silently
|
|
14
|
+
# open arbitrary outbound sockets; even if it reads a listed var it
|
|
15
|
+
# has no `--allow-net` route to exfiltrate it.
|
|
14
16
|
# - `--cached-only` refuses any module that was not baked into the
|
|
15
|
-
# image at build time
|
|
16
|
-
#
|
|
17
|
+
# image at build time, and `--no-lock` keeps the lockfile out of the
|
|
18
|
+
# runtime surface (integrity is already verified against that cache).
|
|
19
|
+
# A malicious republish of a transitive dep cannot ride in via a
|
|
20
|
+
# runtime `import("https://…")`.
|
|
17
21
|
# - Read-only-root-filesystem friendly: no runtime writes. Run with
|
|
18
22
|
# `--read-only --tmpfs /tmp` or set `readOnlyRootFilesystem: true`
|
|
19
23
|
# in your orchestrator.
|
|
@@ -63,4 +67,12 @@ ENTRYPOINT ["/sbin/tini", "--"]
|
|
|
63
67
|
# app genuinely needs them — every flag widens the blast radius of a
|
|
64
68
|
# compromised dependency. If you change `PORT`, update `--allow-net` to
|
|
65
69
|
# match the exposed listen address.
|
|
66
|
-
|
|
70
|
+
#
|
|
71
|
+
# `--allow-env` lists exactly the variables this app reads at startup
|
|
72
|
+
# (see src/main.ts + src/build-app.ts): PORT (listen port), DENO_ENV
|
|
73
|
+
# (production flag), TRUST_PROXY_HOPS (behindProxy), and PUBLIC_URL
|
|
74
|
+
# (OpenAPI server URL). Add a variable here when you read a new one.
|
|
75
|
+
# `--no-lock` skips runtime lockfile access — integrity is already
|
|
76
|
+
# guaranteed by `--cached-only` against the cache baked at build time,
|
|
77
|
+
# and the lockfile is not copied into the runtime stage.
|
|
78
|
+
CMD ["deno", "run", "--cached-only", "--no-lock", "--allow-net=0.0.0.0:3000,127.0.0.1:3000,localhost:3000", "--allow-env=PORT,DENO_ENV,TRUST_PROXY_HOPS,PUBLIC_URL", "--allow-read=/app/deno.json,/app/src", "src/main.ts"]
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"hooks:install": "git config core.hooksPath .githooks"
|
|
11
11
|
},
|
|
12
12
|
"imports": {
|
|
13
|
-
"@daloyjs/core": "jsr:@daloyjs/daloy@^0.
|
|
14
|
-
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.
|
|
15
|
-
"@daloyjs/core/contract": "jsr:@daloyjs/daloy@^0.
|
|
16
|
-
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.
|
|
17
|
-
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.
|
|
13
|
+
"@daloyjs/core": "jsr:@daloyjs/daloy@^0.43.0",
|
|
14
|
+
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.43.0/banner",
|
|
15
|
+
"@daloyjs/core/contract": "jsr:@daloyjs/daloy@^0.43.0/contract",
|
|
16
|
+
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.43.0/deno",
|
|
17
|
+
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.43.0/openapi",
|
|
18
18
|
"zod": "npm:zod@^4.4.3"
|
|
19
19
|
},
|
|
20
20
|
"compilerOptions": {
|