Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| A project is an app or a library: libraries show their package and how to ship a release, not production | 1 | import { test } from "node:test"; |
| 2 | import assert from "node:assert/strict"; | |
| 3 | ||
| 4 | import { type KindFacts, type RootFiles, deploysSetting, detectKind, goFilesToRead, resolveKind } from "./kind.ts"; | |
| 5 | ||
| 6 | const root = (text: Record<string, string>, more: Partial<RootFiles> = {}): RootFiles => ({ | |
| 7 | entries: [...Object.keys(text), ...(more.entries ?? [])], | |
| 8 | text, | |
| 9 | ...(more.src ? { src: more.src } : {}), | |
| 10 | ...(more.public ? { public: more.public } : {}), | |
| 11 | }); | |
| 12 | ||
| 13 | // psr/log's composer.json, as it is at 3.0.2: no type, an autoload. | |
| 14 | const PSR_LOG = JSON.stringify({ | |
| 15 | name: "psr/log", | |
| 16 | description: "Common interface for logging libraries", | |
| 17 | license: "MIT", | |
| 18 | require: { php: ">=8.0.0" }, | |
| 19 | autoload: { "psr-4": { "Psr\\Log\\": "src" } }, | |
| 20 | }); | |
| 21 | ||
| 22 | test("a Composer package is a library; a Composer project is an app", () => { | |
| 23 | assert.deepEqual(detectKind(root({ "composer.json": PSR_LOG }, { entries: ["src/", "README.md"] })), { | |
| 24 | kind: "library", | |
| 25 | detail: "composer.json has autoload and no public/index.php.", | |
| 26 | ecosystem: "composer", | |
| 27 | }); | |
| 28 | assert.equal(detectKind(root({ "composer.json": '{"type":"library"}' })).kind, "library"); | |
| 29 | assert.equal(detectKind(root({ "composer.json": '{"type":"symfony-bundle"}' })).detail, 'composer.json says "type": "symfony-bundle".'); | |
| 30 | assert.equal(detectKind(root({ "composer.json": '{"type":"project","autoload":{}}' })).kind, "app"); | |
| 31 | // Laravel and the like: no type, but a front controller to serve. | |
| 32 | const laravel = root({ "composer.json": '{"autoload":{"psr-4":{}}}' }, { entries: ["public/"], public: ["index.php", ".htaccess"] }); | |
| 33 | assert.equal(detectKind(laravel).kind, "app"); | |
| 34 | }); | |
| 35 | ||
| 36 | test("package.json: an entry point without a start or dev script is a library", () => { | |
| 37 | assert.equal(detectKind(root({ "package.json": '{"name":"x","main":"dist/index.js","scripts":{"build":"tsc"}}' })).kind, "library"); | |
| 38 | assert.equal(detectKind(root({ "package.json": '{"exports":{".":"./index.js"}}' })).detail, 'package.json has "exports" and no start or dev script.'); | |
| 39 | assert.equal(detectKind(root({ "package.json": '{"main":"index.js","scripts":{"start":"node index.js"}}' })).kind, "app"); | |
| 40 | assert.equal(detectKind(root({ "package.json": '{"main":"index.js","scripts":{"dev":"vite"}}' })).kind, "app"); | |
| 41 | assert.equal(detectKind(root({ "package.json": '{"files":["dist"],"dependencies":{"next":"15"}}' })).detail, "package.json depends on next."); | |
| 42 | assert.equal(detectKind(root({ "package.json": '{"files":["dist"],"devDependencies":{"@sveltejs/kit":"2"}}' })).kind, "app"); | |
| 43 | // A library built with Vite stays a library; a Vite site has index.html. | |
| 44 | assert.equal(detectKind(root({ "package.json": '{"main":"dist/x.js","devDependencies":{"vite":"6"}}' })).kind, "library"); | |
| 45 | assert.equal(detectKind(root({ "package.json": '{"main":"x.js","devDependencies":{"vite":"6"}}' }, { entries: ["index.html"] })).kind, "app"); | |
| 46 | // Nothing to tell from. | |
| 47 | assert.equal(detectKind(root({ "package.json": '{"name":"x"}' })).kind, null); | |
| 48 | assert.equal(detectKind(root({ "package.json": "not json" })).kind, null); | |
| 49 | }); | |
| 50 | ||
| 51 | test("Cargo: a library and no binary is a library", () => { | |
| 52 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\nname = \"x\"\n\n[lib]\n" }, { entries: ["src/"], src: ["lib.rs"] })).kind, "library"); | |
| 53 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\nname = \"x\"\n" }, { entries: ["src/"], src: ["lib.rs"] })).kind, "library"); | |
| 54 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\n[lib]\n" }, { entries: ["src/"], src: ["lib.rs", "main.rs"] })).kind, "app"); | |
| 55 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\n[lib]\n[[bin]]\nname = \"x\"\n" })).kind, "app"); | |
| 56 | }); | |
| 57 | ||
| 58 | test("Go: a module with no package main at its root is a library", () => { | |
| 59 | const lib = root({ "go.mod": "module g1t.sh/acme/log\n", "log.go": "// Package log logs.\npackage log\n" }); | |
| 60 | assert.deepEqual(detectKind(lib), { kind: "library", detail: "go.mod, with no package main at the root.", ecosystem: "go" }); | |
| 61 | const app = root({ "go.mod": "module x\n", "main.go": "package main\n\nfunc main() {}\n" }); | |
| 62 | assert.deepEqual(detectKind(app), { kind: "app", detail: "main.go at the root is package main.", ecosystem: "go" }); | |
| 63 | assert.equal(detectKind(root({ "go.mod": "module x\n" }, { entries: ["cmd/"] })).kind, null); | |
| 64 | assert.deepEqual(goFilesToRead(["a.go", "a_test.go", "b.go", "c.go", "d.go", "go.mod"]), ["a.go", "b.go", "c.go"]); | |
| 65 | }); | |
| 66 | ||
| 67 | test("Python: a build backend and no app framework is a library", () => { | |
| 68 | const lib = '[build-system]\nrequires = ["hatchling"]\nbuild-backend = "hatchling.build"\n[project]\ndependencies = ["httpx"]\n'; | |
| 69 | assert.equal(detectKind(root({ "pyproject.toml": lib })).kind, "library"); | |
| 70 | const app = '[build-system]\nbuild-backend = "hatchling.build"\n[project]\ndependencies = ["django>=5", "flask-login"]\n'; | |
| 71 | assert.equal(detectKind(root({ "pyproject.toml": app })).detail, "pyproject.toml depends on django."); | |
| 72 | // A plugin named after a framework is not the framework. | |
| 73 | assert.equal(detectKind(root({ "pyproject.toml": '[build-system]\nbuild-backend = "x"\ndependencies = ["flask-login"]\n' })).kind, "library"); | |
| 74 | }); | |
| 75 | ||
| 76 | test("the language's manifest decides before package.json, and a Workers config is always an app", () => { | |
| 77 | const php = root({ "composer.json": PSR_LOG, "package.json": '{"scripts":{"dev":"vite"}}' }); | |
| 78 | assert.equal(detectKind(php).ecosystem, "composer"); | |
| 79 | assert.equal(detectKind(php).kind, "library"); | |
| 80 | assert.equal(detectKind(root({ "package.json": '{"main":"x.js"}' }, { entries: ["wrangler.jsonc"] })).kind, "app"); | |
| 81 | assert.equal(detectKind(root({}, { entries: ["index.html", "style.css"] })).kind, "app"); | |
| 82 | assert.deepEqual(detectKind(root({}, { entries: ["README.md"] })), { kind: null, detail: "No manifest at the root says what it is.", ecosystem: null }); | |
| 83 | }); | |
| 84 | ||
| 85 | const auto: KindFacts = { deploys: "auto", deploymentsOn: false, linkedPackage: null, detected: null }; | |
| 86 | ||
| 87 | test("the setting wins over everything", () => { | |
| 88 | assert.equal(resolveKind({ ...auto, deploys: "yes", linkedPackage: "Composer package psr/log" }).kind, "app"); | |
| 89 | assert.deepEqual(resolveKind({ ...auto, deploys: "no", deploymentsOn: true }), { | |
| 90 | kind: "library", | |
| 91 | reason: { by: "set", detail: "Set in its settings: it doesn't deploy." }, | |
| 92 | }); | |
| 93 | }); | |
| 94 | ||
| 95 | test("on auto: Deployments on, then packages, then files, then an app", () => { | |
| 96 | const library = { kind: "library" as const, detail: "composer.json has autoload and no public/index.php." }; | |
| 97 | assert.equal(resolveKind({ ...auto, deploymentsOn: true, linkedPackage: "Composer package psr/log", detected: library }).reason.by, "deployments"); | |
| 98 | assert.deepEqual(resolveKind({ ...auto, linkedPackage: "Composer package psr/log", detected: library }), { | |
| 99 | kind: "library", | |
| 100 | reason: { by: "packages", detail: "Its repository publishes the Composer package psr/log." }, | |
| 101 | }); | |
| 102 | assert.deepEqual(resolveKind({ ...auto, detected: library }), { kind: "library", reason: { by: "files", ...{ detail: library.detail } } }); | |
| 103 | assert.equal(resolveKind({ ...auto, detected: { kind: "app", detail: "package.json has a start script." } }).reason.by, "files"); | |
| 104 | assert.deepEqual(resolveKind({ ...auto, deploymentsOn: null, detected: { kind: null, detail: "" } }).kind, "app"); | |
| 105 | assert.equal(resolveKind(auto).reason.by, "default"); | |
| 106 | }); | |
| 107 | ||
| 108 | test("an unknown setting is auto", () => { | |
| 109 | assert.equal(deploysSetting("no"), "no"); | |
| 110 | assert.equal(deploysSetting(null), "auto"); | |
| 111 | assert.equal(deploysSetting("sometimes"), "auto"); | |
| 112 | }); |