Skip to content

g1t/services/projects/src/kind.test.ts

167 lines10,659 bytesCodeBlame

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 production1import { test } from "node:test";
2import assert from "node:assert/strict";
3
Merge branch 'projects-kind-and-links'4import {
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 production15
16const 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.
24const 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
32test("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
46test("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
61test("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
68test("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
77test("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
86test("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'95const 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 production96
97test("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 production100 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 production103 });
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 production105});
106
Merge branch 'projects-kind-and-links'107test("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
122test("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 production134test("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 production141 });
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 production143 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'148test("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
156test("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 production167});

This file's history is long; its oldest lines are credited to the oldest commit read.