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 | ||
| Merge branch 'projects-kind-and-links' | 4 | import { |
| 5 | type KindFacts, | |
| 6 | type RootFiles, | |
| 7 | detectKind, | |
| 8 | goFilesToRead, | |
| 9 | kindSetting, | |
| 10 | neverRuns, | |
| 11 | nextSetting, | |
| 12 | resolveKind, | |
| 13 | runsSetting, | |
| 14 | } from "./kind.ts"; | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 15 | |
| 16 | const root = (text: Record<string, string>, more: Partial<RootFiles> = {}): RootFiles => ({ | |
| 17 | entries: [...Object.keys(text), ...(more.entries ?? [])], | |
| 18 | text, | |
| 19 | ...(more.src ? { src: more.src } : {}), | |
| 20 | ...(more.public ? { public: more.public } : {}), | |
| 21 | }); | |
| 22 | ||
| 23 | // psr/log's composer.json, as it is at 3.0.2: no type, an autoload. | |
| 24 | const PSR_LOG = JSON.stringify({ | |
| 25 | name: "psr/log", | |
| 26 | description: "Common interface for logging libraries", | |
| 27 | license: "MIT", | |
| 28 | require: { php: ">=8.0.0" }, | |
| 29 | autoload: { "psr-4": { "Psr\\Log\\": "src" } }, | |
| 30 | }); | |
| 31 | ||
| 32 | test("a Composer package is a library; a Composer project is an app", () => { | |
| 33 | assert.deepEqual(detectKind(root({ "composer.json": PSR_LOG }, { entries: ["src/", "README.md"] })), { | |
| 34 | kind: "library", | |
| 35 | detail: "composer.json has autoload and no public/index.php.", | |
| 36 | ecosystem: "composer", | |
| 37 | }); | |
| 38 | assert.equal(detectKind(root({ "composer.json": '{"type":"library"}' })).kind, "library"); | |
| 39 | assert.equal(detectKind(root({ "composer.json": '{"type":"symfony-bundle"}' })).detail, 'composer.json says "type": "symfony-bundle".'); | |
| 40 | assert.equal(detectKind(root({ "composer.json": '{"type":"project","autoload":{}}' })).kind, "app"); | |
| 41 | // Laravel and the like: no type, but a front controller to serve. | |
| 42 | const laravel = root({ "composer.json": '{"autoload":{"psr-4":{}}}' }, { entries: ["public/"], public: ["index.php", ".htaccess"] }); | |
| 43 | assert.equal(detectKind(laravel).kind, "app"); | |
| 44 | }); | |
| 45 | ||
| 46 | test("package.json: an entry point without a start or dev script is a library", () => { | |
| 47 | assert.equal(detectKind(root({ "package.json": '{"name":"x","main":"dist/index.js","scripts":{"build":"tsc"}}' })).kind, "library"); | |
| 48 | assert.equal(detectKind(root({ "package.json": '{"exports":{".":"./index.js"}}' })).detail, 'package.json has "exports" and no start or dev script.'); | |
| 49 | assert.equal(detectKind(root({ "package.json": '{"main":"index.js","scripts":{"start":"node index.js"}}' })).kind, "app"); | |
| 50 | assert.equal(detectKind(root({ "package.json": '{"main":"index.js","scripts":{"dev":"vite"}}' })).kind, "app"); | |
| 51 | assert.equal(detectKind(root({ "package.json": '{"files":["dist"],"dependencies":{"next":"15"}}' })).detail, "package.json depends on next."); | |
| 52 | assert.equal(detectKind(root({ "package.json": '{"files":["dist"],"devDependencies":{"@sveltejs/kit":"2"}}' })).kind, "app"); | |
| 53 | // A library built with Vite stays a library; a Vite site has index.html. | |
| 54 | assert.equal(detectKind(root({ "package.json": '{"main":"dist/x.js","devDependencies":{"vite":"6"}}' })).kind, "library"); | |
| 55 | assert.equal(detectKind(root({ "package.json": '{"main":"x.js","devDependencies":{"vite":"6"}}' }, { entries: ["index.html"] })).kind, "app"); | |
| 56 | // Nothing to tell from. | |
| 57 | assert.equal(detectKind(root({ "package.json": '{"name":"x"}' })).kind, null); | |
| 58 | assert.equal(detectKind(root({ "package.json": "not json" })).kind, null); | |
| 59 | }); | |
| 60 | ||
| 61 | test("Cargo: a library and no binary is a library", () => { | |
| 62 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\nname = \"x\"\n\n[lib]\n" }, { entries: ["src/"], src: ["lib.rs"] })).kind, "library"); | |
| 63 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\nname = \"x\"\n" }, { entries: ["src/"], src: ["lib.rs"] })).kind, "library"); | |
| 64 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\n[lib]\n" }, { entries: ["src/"], src: ["lib.rs", "main.rs"] })).kind, "app"); | |
| 65 | assert.equal(detectKind(root({ "Cargo.toml": "[package]\n[lib]\n[[bin]]\nname = \"x\"\n" })).kind, "app"); | |
| 66 | }); | |
| 67 | ||
| 68 | test("Go: a module with no package main at its root is a library", () => { | |
| 69 | const lib = root({ "go.mod": "module g1t.sh/acme/log\n", "log.go": "// Package log logs.\npackage log\n" }); | |
| 70 | assert.deepEqual(detectKind(lib), { kind: "library", detail: "go.mod, with no package main at the root.", ecosystem: "go" }); | |
| 71 | const app = root({ "go.mod": "module x\n", "main.go": "package main\n\nfunc main() {}\n" }); | |
| 72 | assert.deepEqual(detectKind(app), { kind: "app", detail: "main.go at the root is package main.", ecosystem: "go" }); | |
| 73 | assert.equal(detectKind(root({ "go.mod": "module x\n" }, { entries: ["cmd/"] })).kind, null); | |
| 74 | assert.deepEqual(goFilesToRead(["a.go", "a_test.go", "b.go", "c.go", "d.go", "go.mod"]), ["a.go", "b.go", "c.go"]); | |
| 75 | }); | |
| 76 | ||
| 77 | test("Python: a build backend and no app framework is a library", () => { | |
| 78 | const lib = '[build-system]\nrequires = ["hatchling"]\nbuild-backend = "hatchling.build"\n[project]\ndependencies = ["httpx"]\n'; | |
| 79 | assert.equal(detectKind(root({ "pyproject.toml": lib })).kind, "library"); | |
| 80 | const app = '[build-system]\nbuild-backend = "hatchling.build"\n[project]\ndependencies = ["django>=5", "flask-login"]\n'; | |
| 81 | assert.equal(detectKind(root({ "pyproject.toml": app })).detail, "pyproject.toml depends on django."); | |
| 82 | // A plugin named after a framework is not the framework. | |
| 83 | assert.equal(detectKind(root({ "pyproject.toml": '[build-system]\nbuild-backend = "x"\ndependencies = ["flask-login"]\n' })).kind, "library"); | |
| 84 | }); | |
| 85 | ||
| 86 | test("the language's manifest decides before package.json, and a Workers config is always an app", () => { | |
| 87 | const php = root({ "composer.json": PSR_LOG, "package.json": '{"scripts":{"dev":"vite"}}' }); | |
| 88 | assert.equal(detectKind(php).ecosystem, "composer"); | |
| 89 | assert.equal(detectKind(php).kind, "library"); | |
| 90 | assert.equal(detectKind(root({ "package.json": '{"main":"x.js"}' }, { entries: ["wrangler.jsonc"] })).kind, "app"); | |
| 91 | assert.equal(detectKind(root({}, { entries: ["index.html", "style.css"] })).kind, "app"); | |
| 92 | assert.deepEqual(detectKind(root({}, { entries: ["README.md"] })), { kind: null, detail: "No manifest at the root says what it is.", ecosystem: null }); | |
| 93 | }); | |
| 94 | ||
| Merge branch 'projects-kind-and-links' | 95 | const auto: KindFacts = { setting: { kind: null, runs: null }, deploymentsOn: false, linkedPackage: null, detected: null }; |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 96 | |
| 97 | test("the setting wins over everything", () => { | |
| Merge branch 'projects-kind-and-links' | 98 | assert.equal(resolveKind({ ...auto, setting: { kind: "app", runs: "g1t" }, linkedPackage: "Composer package psr/log" }).kind, "app"); |
| 99 | assert.deepEqual(resolveKind({ ...auto, setting: { kind: "library", runs: null }, deploymentsOn: true }), { | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 100 | kind: "library", |
| Merge branch 'projects-kind-and-links' | 101 | reason: { by: "set", detail: "Set in its settings: a library." }, |
| 102 | runs: null, | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 103 | }); |
| Merge branch 'projects-kind-and-links' | 104 | assert.equal(resolveKind({ ...auto, setting: { kind: "tool", runs: null } }).reason.detail, "Set in its settings: a tool."); |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 105 | }); |
| 106 | ||
| Merge branch 'projects-kind-and-links' | 107 | test("where it runs: set, or on g1t while Deployments are on, or nobody has said", () => { |
| 108 | // An app deployed by its own CI: no nag to turn on Deployments. | |
| 109 | const elsewhere = resolveKind({ ...auto, setting: { kind: null, runs: "elsewhere" }, detected: { kind: "library", detail: "" } }); | |
| 110 | assert.deepEqual([elsewhere.kind, elsewhere.runs, elsewhere.reason.by], ["app", "elsewhere", "set"]); | |
| 111 | assert.equal(resolveKind({ ...auto, deploymentsOn: true }).runs, "g1t"); | |
| 112 | assert.equal(resolveKind({ ...auto, setting: { kind: "app", runs: "g1t" } }).runs, "g1t"); | |
| 113 | // Detected an app, Deployments off: unknown, so the page asks instead of nagging. | |
| 114 | assert.equal(resolveKind({ ...auto, detected: { kind: "app", detail: "wrangler.jsonc at the root." } }).runs, null); | |
| 115 | // Docs can be a site; a library, a tool and other never run. | |
| 116 | assert.equal(resolveKind({ ...auto, setting: { kind: "docs", runs: null }, deploymentsOn: true }).runs, "g1t"); | |
| 117 | for (const kind of ["library", "tool", "other"] as const) { | |
| 118 | assert.equal(resolveKind({ ...auto, setting: { kind, runs: "g1t" }, deploymentsOn: true }).runs, null); | |
| 119 | } | |
| 120 | }); | |
| 121 | ||
| 122 | test("a change to the setting keeps it coherent", () => { | |
| 123 | const none = { kind: null, runs: null }; | |
| 124 | assert.deepEqual(nextSetting(none, { runs: "elsewhere" }), { kind: null, runs: "elsewhere" }); | |
| 125 | assert.deepEqual(nextSetting({ kind: "app", runs: "g1t" }, { kind: "library" }), { kind: "library", runs: null }); | |
| 126 | assert.deepEqual(nextSetting({ kind: "docs", runs: "elsewhere" }, { kind: "docs" }), { kind: "docs", runs: "elsewhere" }); | |
| 127 | assert.deepEqual(nextSetting({ kind: "tool", runs: null }, { runs: "g1t" }), { kind: "app", runs: "g1t" }); | |
| 128 | assert.deepEqual(nextSetting({ kind: "app", runs: "g1t" }, { kind: "auto", runs: "auto" }), none); | |
| 129 | assert.equal(neverRuns({ kind: "other", runs: null }), true); | |
| 130 | assert.equal(neverRuns({ kind: "docs", runs: null }), false); | |
| 131 | assert.equal(neverRuns(none), false); | |
| 132 | }); | |
| 133 | ||
| A project is an app or a library: libraries show their package and how to ship a release, not production | 134 | test("on auto: Deployments on, then packages, then files, then an app", () => { |
| 135 | const library = { kind: "library" as const, detail: "composer.json has autoload and no public/index.php." }; | |
| 136 | assert.equal(resolveKind({ ...auto, deploymentsOn: true, linkedPackage: "Composer package psr/log", detected: library }).reason.by, "deployments"); | |
| 137 | assert.deepEqual(resolveKind({ ...auto, linkedPackage: "Composer package psr/log", detected: library }), { | |
| 138 | kind: "library", | |
| 139 | reason: { by: "packages", detail: "Its repository publishes the Composer package psr/log." }, | |
| Merge branch 'projects-kind-and-links' | 140 | runs: null, |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 141 | }); |
| Merge branch 'projects-kind-and-links' | 142 | assert.deepEqual(resolveKind({ ...auto, detected: library }), { kind: "library", reason: { by: "files", detail: library.detail }, runs: null }); |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 143 | assert.equal(resolveKind({ ...auto, detected: { kind: "app", detail: "package.json has a start script." } }).reason.by, "files"); |
| 144 | assert.deepEqual(resolveKind({ ...auto, deploymentsOn: null, detected: { kind: null, detail: "" } }).kind, "app"); | |
| 145 | assert.equal(resolveKind(auto).reason.by, "default"); | |
| 146 | }); | |
| 147 | ||
| Merge branch 'projects-kind-and-links' | 148 | test("an unknown setting is left to detection", () => { |
| 149 | assert.equal(kindSetting("tool"), "tool"); | |
| 150 | assert.equal(kindSetting("auto"), null); | |
| 151 | assert.equal(kindSetting("yes"), null); | |
| 152 | assert.equal(runsSetting("elsewhere"), "elsewhere"); | |
| 153 | assert.equal(runsSetting("sometimes"), null); | |
| 154 | }); | |
| 155 | ||
| 156 | test("detection says tool, and docs, where the files do", () => { | |
| 157 | assert.deepEqual(detectKind(root({ "package.json": '{"name":"x","bin":{"x":"cli.js"}}' })), { | |
| 158 | kind: "tool", | |
| 159 | detail: 'package.json has "bin" and nothing to import.', | |
| 160 | ecosystem: "npm", | |
| 161 | }); | |
| 162 | // Something to import as well as a command: a library. | |
| 163 | assert.equal(detectKind(root({ "package.json": '{"main":"x.js","bin":"cli.js"}' })).kind, "library"); | |
| 164 | assert.equal(detectKind(root({ "package.json": '{"scripts":{"start":"docusaurus start"}}' }, { entries: ["docusaurus.config.ts"] })).kind, "docs"); | |
| 165 | assert.equal(detectKind(root({}, { entries: ["mkdocs.yml", "docs/"] })).detail, "mkdocs.yml at the root builds documentation."); | |
| 166 | assert.equal(resolveKind({ ...auto, detected: { kind: "docs", detail: "mkdocs.yml at the root builds documentation." } }).kind, "docs"); | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 167 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.