create-daloy 0.24.0 → 0.34.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 +18 -5
- package/bin/create-daloy.mjs +130 -37
- package/package.json +5 -3
- package/sbom.cdx.json +56 -0
- package/sbom.spdx.json +42 -0
- package/templates/_ci/deno/SECURITY.md +5 -1
- package/templates/_ci/deno/_github/dependabot.yml +12 -1
- package/templates/_ci/deno/_github/workflows/container-scan.yml +158 -0
- package/templates/_ci/node/SECURITY.md +33 -3
- package/templates/_ci/node/_github/CODEOWNERS +1 -1
- package/templates/_ci/node/_github/dependabot.yml +12 -1
- package/templates/_ci/node/_github/workflows/container-scan.yml +177 -0
- package/templates/_ci/node/_github/workflows/vuln-scan.yml +89 -0
- package/templates/bun-basic/AGENTS.md +10 -0
- package/templates/bun-basic/README.md +10 -0
- package/templates/bun-basic/_Dockerfile +57 -0
- package/templates/bun-basic/_dockerignore +11 -0
- package/templates/bun-basic/package.json +1 -1
- package/templates/cloudflare-worker/_Dockerfile +56 -0
- package/templates/cloudflare-worker/_dockerignore +13 -0
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/cloudflare-worker/src/index.ts +17 -0
- package/templates/deno-basic/_Dockerfile +66 -0
- package/templates/deno-basic/_dockerignore +10 -0
- package/templates/deno-basic/deno.json +2 -2
- package/templates/node-basic/AGENTS.md +10 -0
- package/templates/node-basic/README.md +10 -0
- package/templates/node-basic/_Dockerfile +23 -10
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel-edge/AGENTS.md +10 -0
- package/templates/vercel-edge/README.md +10 -0
- package/templates/vercel-edge/_Dockerfile +55 -0
- package/templates/vercel-edge/_dockerignore +13 -0
- package/templates/vercel-edge/package.json +1 -1
- package/templates/_ci/node/_github/workflows/release.yml +0 -125
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://daloyjs.dev">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://daloyjs.dev/assets/banner-x-1500x500.png">
|
|
5
|
+
<img alt="DaloyJS — Contract-first REST APIs for Node · Bun · Deno · Workers · Edge" src="https://daloyjs.dev/assets/banner-light-1280x426.png" width="100%">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
1
10
|
# create-daloy
|
|
2
11
|
|
|
3
12
|
Scaffold a new [DaloyJS](https://github.com/daloyjs/daloy) project in seconds.
|
|
@@ -144,17 +153,21 @@ For Node-style templates, the bundle adds:
|
|
|
144
153
|
- `.github/workflows/ci.yml` with top-level `permissions: {}`, pinned actions,
|
|
145
154
|
`harden-runner`, `persist-credentials: false`, no package-manager cache, and
|
|
146
155
|
install scripts disabled.
|
|
147
|
-
- `.github/workflows/
|
|
148
|
-
|
|
149
|
-
|
|
156
|
+
- `.github/workflows/vuln-scan.yml` — a daily scheduled SCA cron that runs the
|
|
157
|
+
package manager's audit against the committed lockfile. Catches CVEs disclosed
|
|
158
|
+
*after* the last PR or push and provides SOC 2 CC7.1
|
|
159
|
+
([continuous vulnerability management](https://www.aikido.dev/blog/a-guide-to-automating-technical-vulnerability-management-for-soc-2))
|
|
160
|
+
evidence even when developers are not touching the repo.
|
|
150
161
|
- CodeQL, OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and `SECURITY.md`.
|
|
151
162
|
- `scripts/verify-lockfile-sources.mjs` plus a `verify:lockfile` package script
|
|
152
163
|
that rejects git dependencies and non-registry tarball URLs in text lockfiles.
|
|
153
164
|
|
|
165
|
+
The bundle deliberately does **not** generate an npm publish workflow.
|
|
166
|
+
`create-daloy` scaffolds REST API services, not libraries; if you later carve
|
|
167
|
+
out a reusable package, opt into npm trusted publishing yourself.
|
|
168
|
+
|
|
154
169
|
For `deno-basic`, `--with-ci` generates a Deno-native CI workflow plus CodeQL,
|
|
155
170
|
Scorecard, zizmor, Dependabot for GitHub Actions, CODEOWNERS, and `SECURITY.md`.
|
|
156
|
-
It does not generate an npm release workflow because the Deno template has no
|
|
157
|
-
`package.json`.
|
|
158
171
|
|
|
159
172
|
If you omit `--code-owner`, the generated CODEOWNERS file uses
|
|
160
173
|
`@your-org/security-team` as a placeholder. Replace it before relying on branch
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -250,15 +250,27 @@ function renderBox(lines, options = {}) {
|
|
|
250
250
|
return out.join("\n");
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
253
|
+
// "Flowing waves" banner that mirrors the DaloyJS brand mark: three sine
|
|
254
|
+
// curves cascading left-to-right in sky-blue tones. Each row is rendered
|
|
255
|
+
// with a left-to-right gradient on truecolor terminals, falls back to ANSI
|
|
256
|
+
// cyan on 256-color TTYs, and degrades to ASCII tildes in dumb terminals.
|
|
257
|
+
//
|
|
258
|
+
// "Daloy" means "flow" in Filipino — the three waves represent contracts,
|
|
259
|
+
// requests, and responses moving cleanly between client and server.
|
|
260
|
+
const LOGO_WAVE_LINES = SUPPORTS_UNICODE
|
|
261
|
+
? [
|
|
262
|
+
"\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F",
|
|
263
|
+
"\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F",
|
|
264
|
+
"\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F\u223F",
|
|
265
|
+
]
|
|
266
|
+
: ["~".repeat(25), "~".repeat(25), "~".repeat(25)];
|
|
267
|
+
|
|
268
|
+
// Sky-blue palette tuned to match `tailwindcss` sky-200/400/700 — the same
|
|
269
|
+
// colors used in the wordmark on https://daloyjs.dev.
|
|
270
|
+
const LOGO_WAVE_GRADIENTS = [
|
|
271
|
+
{ start: [186, 230, 253], end: [125, 211, 252] }, // sky-200 -> sky-300
|
|
272
|
+
{ start: [56, 189, 248], end: [14, 165, 233] }, // sky-400 -> sky-500
|
|
273
|
+
{ start: [2, 132, 199], end: [3, 105, 161] }, // sky-600 -> sky-700
|
|
262
274
|
];
|
|
263
275
|
|
|
264
276
|
function gradientLine(line, startRgb, endRgb) {
|
|
@@ -278,25 +290,26 @@ function gradientLine(line, startRgb, endRgb) {
|
|
|
278
290
|
|
|
279
291
|
function printBanner(version) {
|
|
280
292
|
if (!SUPPORTS_UNICODE) {
|
|
281
|
-
console.log(`\n${color(COLORS.bold + COLORS.
|
|
293
|
+
console.log(`\n${color(COLORS.bold + COLORS.cyan, "create-daloy")} ${color(COLORS.dim, `v${version}`)}`);
|
|
282
294
|
console.log(color(COLORS.dim, "Contract-first REST APIs for Node, Bun, Deno, Vercel Edge, and Workers"));
|
|
283
295
|
console.log(color(COLORS.dim, "https://daloyjs.dev\n"));
|
|
284
296
|
return;
|
|
285
297
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const end = [255, 215, 0]; // Gold
|
|
290
|
-
console.log("");
|
|
291
|
-
for (const line of LOGO_LINES) {
|
|
292
|
-
console.log(` ${gradientLine(line, start, end)}`);
|
|
298
|
+
for (let i = 0; i < LOGO_WAVE_LINES.length; i += 1) {
|
|
299
|
+
const { start, end } = LOGO_WAVE_GRADIENTS[i];
|
|
300
|
+
console.log(` ${gradientLine(LOGO_WAVE_LINES[i], start, end)}`);
|
|
293
301
|
}
|
|
302
|
+
// Centered wordmark beneath the waves: "Daloy" in neutral text, "JS" in
|
|
303
|
+
// brand sky-blue. Mirrors the SVG lockup used on the website.
|
|
304
|
+
const wordmark = `${color(COLORS.bold + COLORS.white, "Daloy")}${color(COLORS.bold + COLORS.cyan, "JS")}`;
|
|
305
|
+
const wordmarkPadding = " ".repeat(Math.max(0, Math.floor((stringWidth(LOGO_WAVE_LINES[0]) - 7) / 2)));
|
|
306
|
+
console.log(` ${wordmarkPadding}${wordmark}`);
|
|
294
307
|
// Build the welcome content lines (each contains its own ANSI color codes).
|
|
295
|
-
const headline = `${color(COLORS.bold + COLORS.
|
|
308
|
+
const headline = `${color(COLORS.bold + COLORS.cyan, "Welcome to DaloyJS")} ${color(COLORS.gray, `\u2014 v${version}`)}`;
|
|
296
309
|
const subline = color(COLORS.dim, "Contract-first REST APIs for Node, Bun, Deno, Vercel Edge, and Workers.");
|
|
297
310
|
const docs = `${color(COLORS.gray, "docs:")} ${color(COLORS.cyan, "https://daloyjs.dev/docs")}`;
|
|
298
311
|
console.log("");
|
|
299
|
-
console.log(renderBox([headline, subline, "", docs], { accent: COLORS.
|
|
312
|
+
console.log(renderBox([headline, subline, "", docs], { accent: COLORS.cyan }));
|
|
300
313
|
console.log("");
|
|
301
314
|
}
|
|
302
315
|
|
|
@@ -556,6 +569,73 @@ async function normalizePackageManagerFiles(dir, packageManager) {
|
|
|
556
569
|
}
|
|
557
570
|
}
|
|
558
571
|
|
|
572
|
+
function dockerInstallSnippet(packageManager) {
|
|
573
|
+
if (packageManager === "npm") {
|
|
574
|
+
return {
|
|
575
|
+
copy: "COPY package.json package-lock.json* npm-shrinkwrap.json* ./",
|
|
576
|
+
run: "RUN npm ci --ignore-scripts",
|
|
577
|
+
text: "npm ci --ignore-scripts",
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (packageManager === "yarn") {
|
|
581
|
+
return {
|
|
582
|
+
copy: "COPY package.json yarn.lock* ./",
|
|
583
|
+
run: "RUN corepack enable && yarn install --frozen-lockfile --ignore-scripts",
|
|
584
|
+
text: "yarn install --frozen-lockfile --ignore-scripts",
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (packageManager === "bun") {
|
|
588
|
+
return {
|
|
589
|
+
copy: "COPY package.json bun.lock* bun.lockb* ./",
|
|
590
|
+
run: "RUN bun install --frozen-lockfile --ignore-scripts",
|
|
591
|
+
text: "bun install --frozen-lockfile --ignore-scripts",
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
copy: "COPY package.json pnpm-lock.yaml* ./",
|
|
596
|
+
run: "RUN corepack enable && corepack prepare pnpm@latest --activate && \\\n pnpm install --frozen-lockfile --ignore-scripts",
|
|
597
|
+
text: "pnpm install --frozen-lockfile --ignore-scripts",
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function patchDockerfileForPackageManager(dir, packageManager) {
|
|
602
|
+
const file = path.join(dir, "Dockerfile");
|
|
603
|
+
if (!existsSync(file)) return;
|
|
604
|
+
|
|
605
|
+
const raw = await readFile(file, "utf8");
|
|
606
|
+
const install = dockerInstallSnippet(packageManager);
|
|
607
|
+
const defaultPnpmInstallBlock = [
|
|
608
|
+
"COPY package.json pnpm-lock.yaml* ./",
|
|
609
|
+
"RUN corepack enable && corepack prepare pnpm@latest --activate && \\",
|
|
610
|
+
" pnpm install --frozen-lockfile --ignore-scripts",
|
|
611
|
+
].join("\n");
|
|
612
|
+
let next = raw.replace(
|
|
613
|
+
"COPY package.json pnpm-lock.yaml* ./\nRUN corepack enable && corepack prepare pnpm@latest --activate && \\\n pnpm install --frozen-lockfile --ignore-scripts",
|
|
614
|
+
`${install.copy}\n${install.run}`,
|
|
615
|
+
);
|
|
616
|
+
next = next.replace(defaultPnpmInstallBlock, `${install.copy}\n${install.run}`);
|
|
617
|
+
|
|
618
|
+
if (packageManager !== "pnpm") {
|
|
619
|
+
next = next.replaceAll(
|
|
620
|
+
"pnpm install --frozen-lockfile --ignore-scripts",
|
|
621
|
+
install.text,
|
|
622
|
+
);
|
|
623
|
+
next = next.replaceAll("pnpm build", runScriptCommand(packageManager, "build"));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (packageManager === "bun" && next.includes("FROM ${NODE_IMAGE} AS builder")) {
|
|
627
|
+
if (!next.includes("ARG BUN_IMAGE=")) {
|
|
628
|
+
next = next.replace(
|
|
629
|
+
"ARG NODE_IMAGE=node:24-alpine\n",
|
|
630
|
+
"ARG NODE_IMAGE=node:24-alpine\nARG BUN_IMAGE=oven/bun:1-alpine\n",
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
next = next.replace("FROM ${NODE_IMAGE} AS builder", "FROM ${BUN_IMAGE} AS builder");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (next !== raw) await writeFile(file, next, "utf8");
|
|
637
|
+
}
|
|
638
|
+
|
|
559
639
|
function hasPackageScript(packageJson, scriptName) {
|
|
560
640
|
return typeof packageJson?.scripts?.[scriptName] === "string";
|
|
561
641
|
}
|
|
@@ -584,6 +664,21 @@ function auditCommand(packageManager) {
|
|
|
584
664
|
return "";
|
|
585
665
|
}
|
|
586
666
|
|
|
667
|
+
function auditStepName(packageManager, suffix = "") {
|
|
668
|
+
const baseName = packageManager === "bun" ? "Audit dependencies" : "Audit production dependencies";
|
|
669
|
+
return suffix ? `${baseName} ${suffix}` : baseName;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Extra full-tree audit (includes devDependencies) for package managers that
|
|
673
|
+
// expose separate prod/full scopes. Surfaced for triage but non-blocking so a
|
|
674
|
+
// low-severity dev-tool advisory does not page the on-call.
|
|
675
|
+
function auditFullCommand(packageManager) {
|
|
676
|
+
if (packageManager === "pnpm") return "pnpm audit";
|
|
677
|
+
if (packageManager === "npm") return "npm audit";
|
|
678
|
+
if (packageManager === "yarn") return "yarn audit";
|
|
679
|
+
return "";
|
|
680
|
+
}
|
|
681
|
+
|
|
587
682
|
function setupPackageManagerStep(packageManager) {
|
|
588
683
|
if (packageManager === "pnpm") {
|
|
589
684
|
return ` - name: Set up pnpm
|
|
@@ -612,15 +707,6 @@ function workflowStep(name, command) {
|
|
|
612
707
|
run: ${command}`;
|
|
613
708
|
}
|
|
614
709
|
|
|
615
|
-
function multilineWorkflowStep(name, command) {
|
|
616
|
-
return ` - name: ${name}
|
|
617
|
-
run: |
|
|
618
|
-
${command
|
|
619
|
-
.split("\n")
|
|
620
|
-
.map((line) => ` ${line}`)
|
|
621
|
-
.join("\n")}`;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
710
|
async function readPackageJsonIfPresent(dir) {
|
|
625
711
|
const file = path.join(dir, "package.json");
|
|
626
712
|
if (!existsSync(file)) return null;
|
|
@@ -643,15 +729,18 @@ function renderCiReplacements({ packageManager, template, packageJson, codeOwner
|
|
|
643
729
|
const setupPm = setupPackageManagerStep(packageManager);
|
|
644
730
|
const needsBunRuntime = template === "bun-basic" && packageManager !== "bun";
|
|
645
731
|
const audit = auditCommand(packageManager);
|
|
732
|
+
const auditFull = auditFullCommand(packageManager);
|
|
646
733
|
const buildStep = hasPackageScript(packageJson, "build") ? workflowStep("Build", runScriptCommand(packageManager, "build")) : "";
|
|
647
|
-
const auditStep = audit ? workflowStep(
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
734
|
+
const auditStep = audit ? workflowStep(auditStepName(packageManager), audit) : "";
|
|
735
|
+
// vuln-scan.yml: production-tree audit is blocking, full-tree audit is
|
|
736
|
+
// advisory (continue-on-error) so a low-severity dev-tool advisory does
|
|
737
|
+
// not page the on-call on a daily cron.
|
|
738
|
+
const auditProdStep = audit
|
|
739
|
+
? workflowStep(auditStepName(packageManager, "(blocking)"), audit)
|
|
740
|
+
: workflowStep("No package-manager audit available", "echo 'No audit command available for this package manager; consider switching to npm/pnpm/yarn/bun.'");
|
|
741
|
+
const auditFullStep = auditFull
|
|
742
|
+
? ` - name: Audit full dependency tree (advisory)\n run: ${auditFull}\n continue-on-error: true`
|
|
743
|
+
: "";
|
|
655
744
|
|
|
656
745
|
return new Map([
|
|
657
746
|
["__CODE_OWNER__", codeOwner],
|
|
@@ -663,7 +752,8 @@ fi`;
|
|
|
663
752
|
["__TEST_COMMAND__", runScriptCommand(packageManager, "test")],
|
|
664
753
|
["__BUILD_STEP__", buildStep],
|
|
665
754
|
["__AUDIT_STEP__", auditStep],
|
|
666
|
-
["
|
|
755
|
+
["__AUDIT_PROD_STEP__", auditProdStep],
|
|
756
|
+
["__AUDIT_FULL_STEP__", auditFullStep],
|
|
667
757
|
]);
|
|
668
758
|
}
|
|
669
759
|
|
|
@@ -1305,6 +1395,9 @@ async function main() {
|
|
|
1305
1395
|
await patchPackageJson(targetDir, projectName, packageManager);
|
|
1306
1396
|
logStep("Package metadata written", projectName);
|
|
1307
1397
|
await patchTemplateTextFiles(targetDir, packageManager);
|
|
1398
|
+
if (packageManager !== "pnpm") {
|
|
1399
|
+
await patchDockerfileForPackageManager(targetDir, packageManager);
|
|
1400
|
+
}
|
|
1308
1401
|
await normalizePackageManagerFiles(targetDir, packageManager);
|
|
1309
1402
|
if (packageManager !== "pnpm") {
|
|
1310
1403
|
logStep("Package-manager config normalized", packageManager);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-daloy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"bin",
|
|
37
37
|
"templates",
|
|
38
|
-
"README.md"
|
|
38
|
+
"README.md",
|
|
39
|
+
"sbom.cdx.json",
|
|
40
|
+
"sbom.spdx.json"
|
|
39
41
|
],
|
|
40
42
|
"publishConfig": {
|
|
41
43
|
"access": "public"
|
|
@@ -43,4 +45,4 @@
|
|
|
43
45
|
"scripts": {
|
|
44
46
|
"test": "node --test test/**/*.test.mjs"
|
|
45
47
|
}
|
|
46
|
-
}
|
|
48
|
+
}
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bomFormat": "CycloneDX",
|
|
3
|
+
"specVersion": "1.5",
|
|
4
|
+
"serialNumber": "urn:uuid:82c87bf3-10d1-59f3-9cc6-48dd49603d42",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"metadata": {
|
|
7
|
+
"timestamp": "2026-05-21T23:55:46.185Z",
|
|
8
|
+
"tools": [
|
|
9
|
+
{
|
|
10
|
+
"vendor": "DaloyJS",
|
|
11
|
+
"name": "daloy-generate-sbom",
|
|
12
|
+
"version": "0.34.0"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"authors": [],
|
|
16
|
+
"component": {
|
|
17
|
+
"type": "library",
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@0.34.0",
|
|
19
|
+
"name": "create-daloy",
|
|
20
|
+
"version": "0.34.0",
|
|
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.34.0",
|
|
23
|
+
"licenses": [
|
|
24
|
+
{
|
|
25
|
+
"license": {
|
|
26
|
+
"id": "MIT"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"externalReferences": [
|
|
31
|
+
{
|
|
32
|
+
"type": "vcs",
|
|
33
|
+
"url": "https://github.com/daloyjs/daloy"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "website",
|
|
37
|
+
"url": "https://github.com/daloyjs/daloy/tree/main/packages/create-daloy#readme"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"swid": {
|
|
41
|
+
"tagId": "swidtag-create-daloy-0.34.0",
|
|
42
|
+
"name": "create-daloy",
|
|
43
|
+
"version": "0.34.0",
|
|
44
|
+
"tagVersion": 0,
|
|
45
|
+
"patch": false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"components": [],
|
|
50
|
+
"dependencies": [
|
|
51
|
+
{
|
|
52
|
+
"ref": "pkg:npm/create-daloy@0.34.0",
|
|
53
|
+
"dependsOn": []
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
package/sbom.spdx.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spdxVersion": "SPDX-2.3",
|
|
3
|
+
"dataLicense": "CC0-1.0",
|
|
4
|
+
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
+
"name": "create-daloy-0.34.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.34.0-82c87bf3-10d1-59f3-9cc6-48dd49603d42",
|
|
7
|
+
"creationInfo": {
|
|
8
|
+
"created": "2026-05-21T23:55:46.185Z",
|
|
9
|
+
"creators": [
|
|
10
|
+
"Tool: daloy-generate-sbom",
|
|
11
|
+
"Organization: DaloyJS"
|
|
12
|
+
],
|
|
13
|
+
"licenseListVersion": "3.24"
|
|
14
|
+
},
|
|
15
|
+
"packages": [
|
|
16
|
+
{
|
|
17
|
+
"SPDXID": "SPDXRef-Package-create-daloy",
|
|
18
|
+
"name": "create-daloy",
|
|
19
|
+
"versionInfo": "0.34.0",
|
|
20
|
+
"downloadLocation": "https://github.com/daloyjs/daloy",
|
|
21
|
+
"filesAnalyzed": false,
|
|
22
|
+
"licenseConcluded": "MIT",
|
|
23
|
+
"licenseDeclared": "MIT",
|
|
24
|
+
"copyrightText": "NOASSERTION",
|
|
25
|
+
"supplier": "NOASSERTION",
|
|
26
|
+
"externalRefs": [
|
|
27
|
+
{
|
|
28
|
+
"referenceCategory": "PACKAGE-MANAGER",
|
|
29
|
+
"referenceType": "purl",
|
|
30
|
+
"referenceLocator": "pkg:npm/create-daloy@0.34.0"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"relationships": [
|
|
36
|
+
{
|
|
37
|
+
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
38
|
+
"relationshipType": "DESCRIBES",
|
|
39
|
+
"relatedSpdxElement": "SPDXRef-Package-create-daloy"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -20,7 +20,11 @@ The `--with-ci` bundle adds these defaults:
|
|
|
20
20
|
- Third-party actions are pinned to commit SHAs.
|
|
21
21
|
- `actions/checkout` uses `persist-credentials: false`.
|
|
22
22
|
- Deno CI runs `deno task typecheck` and `deno task test` with the template's narrow permission flags.
|
|
23
|
-
-
|
|
23
|
+
- If the generated project keeps the `Dockerfile`, container scanning lints it
|
|
24
|
+
with hadolint, scans the source tree and built image with Trivy, and warns
|
|
25
|
+
when `FROM` lines are not pinned to an immutable `@sha256:` digest.
|
|
26
|
+
- CodeQL, OpenSSF Scorecard, zizmor, Dependabot for GitHub Actions and Docker
|
|
27
|
+
base images, and CODEOWNERS are generated.
|
|
24
28
|
|
|
25
29
|
## Required repository settings
|
|
26
30
|
|
|
@@ -12,4 +12,15 @@ updates:
|
|
|
12
12
|
patterns:
|
|
13
13
|
- "*"
|
|
14
14
|
commit-message:
|
|
15
|
-
prefix: "chore(actions)"
|
|
15
|
+
prefix: "chore(actions)"
|
|
16
|
+
|
|
17
|
+
# Keep the Deno runtime base image (Dockerfile `FROM`) patched. Combined
|
|
18
|
+
# with the `container-scan.yml` workflow, this catches base-image CVEs early.
|
|
19
|
+
- package-ecosystem: docker
|
|
20
|
+
directory: "/"
|
|
21
|
+
schedule:
|
|
22
|
+
interval: weekly
|
|
23
|
+
day: monday
|
|
24
|
+
open-pull-requests-limit: 5
|
|
25
|
+
commit-message:
|
|
26
|
+
prefix: "chore(docker)"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Container security scan generated by create-daloy --with-ci.
|
|
2
|
+
#
|
|
3
|
+
# The Deno template has no npm package manager, but its Dockerfile still
|
|
4
|
+
# deserves the same container review as Node-based scaffolds:
|
|
5
|
+
#
|
|
6
|
+
# - hadolint lints the Dockerfile for known anti-patterns and unsafe
|
|
7
|
+
# instructions (CIS Docker Benchmark coverage).
|
|
8
|
+
# - Trivy scans the source tree (config + secrets + vulnerable lockfile
|
|
9
|
+
# entries), then scans the built image for OS + language CVEs.
|
|
10
|
+
# - SARIF results are uploaded to the Code Scanning tab so findings show
|
|
11
|
+
# up in the same place as CodeQL.
|
|
12
|
+
|
|
13
|
+
name: Container scan
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
pull_request:
|
|
17
|
+
paths:
|
|
18
|
+
- "Dockerfile"
|
|
19
|
+
- ".dockerignore"
|
|
20
|
+
- "deno.json"
|
|
21
|
+
- "deno.lock"
|
|
22
|
+
- ".github/workflows/container-scan.yml"
|
|
23
|
+
push:
|
|
24
|
+
branches: [main]
|
|
25
|
+
paths:
|
|
26
|
+
- "Dockerfile"
|
|
27
|
+
- ".dockerignore"
|
|
28
|
+
- "deno.json"
|
|
29
|
+
- "deno.lock"
|
|
30
|
+
- ".github/workflows/container-scan.yml"
|
|
31
|
+
schedule:
|
|
32
|
+
# Weekly run catches newly-disclosed base-image CVEs even when the
|
|
33
|
+
# Dockerfile itself has not changed.
|
|
34
|
+
- cron: "17 6 * * 1"
|
|
35
|
+
|
|
36
|
+
permissions: {}
|
|
37
|
+
|
|
38
|
+
concurrency:
|
|
39
|
+
group: container-scan-${{ github.workflow }}-${{ github.ref }}
|
|
40
|
+
cancel-in-progress: true
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
scan:
|
|
44
|
+
name: Lint + scan container
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 20
|
|
47
|
+
permissions:
|
|
48
|
+
contents: read
|
|
49
|
+
security-events: write
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- name: Harden runner
|
|
53
|
+
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
|
|
54
|
+
with:
|
|
55
|
+
egress-policy: audit
|
|
56
|
+
disable-sudo: true
|
|
57
|
+
|
|
58
|
+
- name: Checkout
|
|
59
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
60
|
+
with:
|
|
61
|
+
persist-credentials: false
|
|
62
|
+
show-progress: false
|
|
63
|
+
|
|
64
|
+
- name: Detect Dockerfile
|
|
65
|
+
id: detect
|
|
66
|
+
run: |
|
|
67
|
+
if [ -f Dockerfile ]; then
|
|
68
|
+
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
69
|
+
else
|
|
70
|
+
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
71
|
+
echo "::notice::No Dockerfile found at the repo root; skipping container scan."
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
- name: Pin check (FROM @sha256 digest)
|
|
75
|
+
# Floating tags like `denoland/deno:alpine` silently re-resolve to
|
|
76
|
+
# fresh digests on every build. Pinning `FROM <image>@sha256:<digest>`
|
|
77
|
+
# makes the base reproducible; the Docker Dependabot ecosystem then
|
|
78
|
+
# opens PRs when newer digests are published.
|
|
79
|
+
if: steps.detect.outputs.present == 'true'
|
|
80
|
+
run: |
|
|
81
|
+
unpinned=0
|
|
82
|
+
while IFS= read -r line; do
|
|
83
|
+
ref="${line#FROM }"
|
|
84
|
+
ref="${ref#from }"
|
|
85
|
+
ref="${ref%% AS *}"
|
|
86
|
+
ref="${ref%% as *}"
|
|
87
|
+
ref="${ref## }"
|
|
88
|
+
ref="${ref%% }"
|
|
89
|
+
case "$ref" in
|
|
90
|
+
scratch|"\${"*|*"@sha256:"*) ;;
|
|
91
|
+
*)
|
|
92
|
+
echo "::warning file=Dockerfile::FROM '$ref' is not pinned to a @sha256:<digest>. Override at build time with --build-arg DENO_IMAGE=denoland/deno:alpine@sha256:<digest> or hard-code the digest and let Dependabot keep it fresh."
|
|
93
|
+
unpinned=$((unpinned + 1))
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done < <(grep -E '^(FROM|from) ' Dockerfile || true)
|
|
97
|
+
if [ "$unpinned" -gt 0 ]; then
|
|
98
|
+
echo "::notice::$unpinned unpinned FROM line(s)."
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
- name: Lint Dockerfile (hadolint)
|
|
102
|
+
if: steps.detect.outputs.present == 'true'
|
|
103
|
+
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
|
104
|
+
with:
|
|
105
|
+
dockerfile: Dockerfile
|
|
106
|
+
format: sarif
|
|
107
|
+
output-file: hadolint.sarif
|
|
108
|
+
no-fail: true
|
|
109
|
+
|
|
110
|
+
- name: Upload hadolint SARIF
|
|
111
|
+
if: steps.detect.outputs.present == 'true'
|
|
112
|
+
uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
|
|
113
|
+
with:
|
|
114
|
+
sarif_file: hadolint.sarif
|
|
115
|
+
category: hadolint
|
|
116
|
+
|
|
117
|
+
- name: Trivy filesystem scan (config + secrets + vulns)
|
|
118
|
+
if: steps.detect.outputs.present == 'true'
|
|
119
|
+
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
120
|
+
with:
|
|
121
|
+
scan-type: fs
|
|
122
|
+
scan-ref: .
|
|
123
|
+
severity: HIGH,CRITICAL
|
|
124
|
+
ignore-unfixed: true
|
|
125
|
+
format: sarif
|
|
126
|
+
output: trivy-fs.sarif
|
|
127
|
+
exit-code: "0"
|
|
128
|
+
|
|
129
|
+
- name: Upload Trivy filesystem SARIF
|
|
130
|
+
if: steps.detect.outputs.present == 'true'
|
|
131
|
+
uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
|
|
132
|
+
with:
|
|
133
|
+
sarif_file: trivy-fs.sarif
|
|
134
|
+
category: trivy-fs
|
|
135
|
+
|
|
136
|
+
- name: Build image (no push)
|
|
137
|
+
if: steps.detect.outputs.present == 'true'
|
|
138
|
+
run: |
|
|
139
|
+
docker build --pull --load -t local/app:scan .
|
|
140
|
+
|
|
141
|
+
- name: Trivy image scan
|
|
142
|
+
if: steps.detect.outputs.present == 'true'
|
|
143
|
+
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
144
|
+
with:
|
|
145
|
+
image-ref: local/app:scan
|
|
146
|
+
severity: HIGH,CRITICAL
|
|
147
|
+
ignore-unfixed: true
|
|
148
|
+
format: sarif
|
|
149
|
+
output: trivy-image.sarif
|
|
150
|
+
exit-code: "1"
|
|
151
|
+
vuln-type: os,library
|
|
152
|
+
|
|
153
|
+
- name: Upload Trivy image SARIF
|
|
154
|
+
if: always() && steps.detect.outputs.present == 'true'
|
|
155
|
+
uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
|
|
156
|
+
with:
|
|
157
|
+
sarif_file: trivy-image.sarif
|
|
158
|
+
category: trivy-image
|
|
@@ -23,7 +23,38 @@ The `--with-ci` bundle adds these defaults:
|
|
|
23
23
|
- Package-manager caches are disabled in CI to avoid cache-poisoning bridges.
|
|
24
24
|
- Lockfile source verification rejects git dependencies and non-registry tarball URLs.
|
|
25
25
|
- CodeQL, OpenSSF Scorecard, zizmor, Dependabot, and CODEOWNERS are generated.
|
|
26
|
-
-
|
|
26
|
+
- A daily scheduled `vuln-scan.yml` runs the package manager's audit against the committed lockfile so newly-disclosed CVEs are surfaced even when no PR or push has run CI (SOC 2 CC7.1 continuous-vulnerability-management evidence).
|
|
27
|
+
- No npm publish workflow is generated: this scaffold is a REST API service, not a published package. If you later carve out a reusable library you can opt into npm trusted publishing yourself.
|
|
28
|
+
|
|
29
|
+
## Container hardening
|
|
30
|
+
|
|
31
|
+
If you scaffolded the `node-basic` template (the only one that ships a
|
|
32
|
+
`Dockerfile`), the following defaults are also enabled. They cover the same
|
|
33
|
+
ground the Snyk container-security guidance recommends, using only free,
|
|
34
|
+
SHA-pinned open-source tooling:
|
|
35
|
+
|
|
36
|
+
- Runtime stage is `node:24-alpine` with `tini` as PID 1 and **no `curl`** —
|
|
37
|
+
the `HEALTHCHECK` uses BusyBox `wget` already shipped in the image.
|
|
38
|
+
- Base image is consumed through `ARG NODE_IMAGE`, so production builds can
|
|
39
|
+
pin to an immutable digest: `docker build --build-arg NODE_IMAGE=node:24-alpine@sha256:<digest> .`.
|
|
40
|
+
- App runs as a non-root user (uid 1001) with `STOPSIGNAL SIGTERM`; the
|
|
41
|
+
framework's graceful-shutdown drain fires on container stop.
|
|
42
|
+
- `Dockerfile` is monitored by Dependabot's `docker` ecosystem so the base
|
|
43
|
+
image gets bumped automatically when CVEs land.
|
|
44
|
+
- The `.github/workflows/container-scan.yml` workflow runs on every PR
|
|
45
|
+
and weekly on `main`:
|
|
46
|
+
- **Pin check** emits a PR annotation when any `FROM` line is not
|
|
47
|
+
pinned to a `@sha256:<digest>` (Aikido x Root.io 2026,
|
|
48
|
+
[Harden your containers without the headaches](https://www.aikido.dev/blog/aikido-x-root-io-harden-your-containers-without-the-headaches)).
|
|
49
|
+
Non-blocking so a fresh scaffold goes green; pair with the `docker`
|
|
50
|
+
Dependabot ecosystem to keep the pinned digest current.
|
|
51
|
+
- **hadolint** lints the `Dockerfile` (CIS Docker Benchmark coverage).
|
|
52
|
+
- **Trivy** scans the source tree for secrets, config issues, and
|
|
53
|
+
vulnerable lockfile entries.
|
|
54
|
+
- **Trivy** builds the image and scans it for OS + language CVEs
|
|
55
|
+
(`HIGH`/`CRITICAL`, `--ignore-unfixed`); merges block on CRITICAL.
|
|
56
|
+
- All findings are uploaded as SARIF to the GitHub **Code Scanning**
|
|
57
|
+
tab alongside CodeQL.
|
|
27
58
|
|
|
28
59
|
## Required repository settings
|
|
29
60
|
|
|
@@ -32,5 +63,4 @@ Before relying on these files for a company project:
|
|
|
32
63
|
1. Replace `@your-org/security-team` in `.github/CODEOWNERS` or pass `--code-owner` when scaffolding.
|
|
33
64
|
2. Protect the `main` branch and require the CI, CodeQL, Scorecard, and zizmor checks.
|
|
34
65
|
3. Enable GitHub secret scanning and push protection.
|
|
35
|
-
4.
|
|
36
|
-
5. Keep `ignore-scripts=true` and the `pnpm-workspace.yaml` supply-chain settings on when using pnpm.
|
|
66
|
+
4. Keep `ignore-scripts=true` and the `pnpm-workspace.yaml` supply-chain settings on when using pnpm.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Workflow / CI / CD.
|
|
7
7
|
/.github/ __CODE_OWNER__
|
|
8
8
|
/.github/workflows/ __CODE_OWNER__
|
|
9
|
-
/.github/workflows/
|
|
9
|
+
/.github/workflows/vuln-scan.yml __CODE_OWNER__
|
|
10
10
|
/.github/dependabot.yml __CODE_OWNER__
|
|
11
11
|
/.github/CODEOWNERS __CODE_OWNER__
|
|
12
12
|
|
|
@@ -25,4 +25,15 @@ updates:
|
|
|
25
25
|
dev-dependencies:
|
|
26
26
|
dependency-type: development
|
|
27
27
|
commit-message:
|
|
28
|
-
prefix: "chore(deps)"
|
|
28
|
+
prefix: "chore(deps)"
|
|
29
|
+
|
|
30
|
+
# Keep the runtime base image (Dockerfile `FROM`) patched. Combined with
|
|
31
|
+
# the `container-scan.yml` workflow, this catches base-image CVEs early.
|
|
32
|
+
- package-ecosystem: docker
|
|
33
|
+
directory: "/"
|
|
34
|
+
schedule:
|
|
35
|
+
interval: weekly
|
|
36
|
+
day: monday
|
|
37
|
+
open-pull-requests-limit: 5
|
|
38
|
+
commit-message:
|
|
39
|
+
prefix: "chore(docker)"
|