tegami 0.0.1 → 0.1.0-beta.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.
@@ -0,0 +1,278 @@
1
+ import { r as isNodeError, t as execFailure } from "../error-DBK-9uBa.js";
2
+ import { n as WorkspacePackage } from "../graph-CUgwuRW5.js";
3
+ import { i as pnpmWorkspaceSchema, r as packageManifestSchema } from "../schemas-Cc4h6bq5.js";
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { x } from "tinyexec";
7
+ import * as semver from "semver";
8
+ import { glob } from "tinyglobby";
9
+ import { load } from "js-yaml";
10
+ import { detect } from "package-manager-detector";
11
+ //#region src/providers/npm.ts
12
+ const DEP_FIELDS = [
13
+ "dependencies",
14
+ "devDependencies",
15
+ "peerDependencies",
16
+ "optionalDependencies"
17
+ ];
18
+ var NpmPackage = class extends WorkspacePackage {
19
+ path;
20
+ manifest;
21
+ manager = "npm";
22
+ constructor(path, manifest) {
23
+ super();
24
+ this.path = path;
25
+ this.manifest = manifest;
26
+ }
27
+ get name() {
28
+ return this.manifest.name;
29
+ }
30
+ get version() {
31
+ return this.manifest.version ?? "0.0.0";
32
+ }
33
+ async write() {
34
+ await writeFile(join(this.path, "package.json"), `${JSON.stringify(this.manifest, null, 2)}\n`);
35
+ }
36
+ onPlan(context) {
37
+ const defaults = super.onPlan(context);
38
+ defaults.publish ??= this.manifest.private !== true;
39
+ if (this.manifest.publishConfig?.tag) {
40
+ defaults.npm ??= {};
41
+ defaults.npm.distTag ??= this.manifest.publishConfig.tag;
42
+ }
43
+ return defaults;
44
+ }
45
+ };
46
+ var NpmRegistryClient = class {
47
+ cwd;
48
+ client;
49
+ id = "npm";
50
+ #versionMap = /* @__PURE__ */ new Map();
51
+ constructor(cwd, client, _graph) {
52
+ this.cwd = cwd;
53
+ this.client = client;
54
+ }
55
+ supports(pkg) {
56
+ return pkg instanceof NpmPackage;
57
+ }
58
+ async isPackagePublished(pkg) {
59
+ const cacheKey = `${pkg.id}@${pkg.version}`;
60
+ let info = this.#versionMap.get(cacheKey);
61
+ if (!info) {
62
+ const run = async () => {
63
+ const registry = pkg.manifest.publishConfig?.registry;
64
+ const args = [
65
+ "view",
66
+ `${pkg.name}@${pkg.version}`,
67
+ "version",
68
+ "--json"
69
+ ];
70
+ if (registry) args.push("--registry", registry);
71
+ const result = await x(this.client, args, { nodeOptions: { cwd: this.cwd } });
72
+ if (result.exitCode === 0) return true;
73
+ const output = commandOutput(result);
74
+ if (isMissingRegistryEntry(output)) return false;
75
+ throw new Error(`Unable to validate ${pkg.name}@${pkg.version} against the npm registry${registry ? ` "${registry}"` : ""}: ${output.trim() || `command exited with code ${result.exitCode}`}`);
76
+ };
77
+ info = run();
78
+ this.#versionMap.set(cacheKey, info);
79
+ }
80
+ return info;
81
+ }
82
+ async publish(pkg, { packageStore }) {
83
+ const args = ["publish"];
84
+ const distTag = packageStore.npm?.distTag;
85
+ if (distTag) args.push("--tag", distTag);
86
+ if (this.client === "pnpm") args.push("--no-git-checks");
87
+ const result = await x(this.client, args, { nodeOptions: { cwd: pkg.path } });
88
+ if (result.exitCode !== 0) throw execFailure(`Failed to publish ${pkg.name}@${pkg.version}${distTag ? ` with dist-tag "${distTag}"` : ""}.`, result);
89
+ }
90
+ };
91
+ function commandOutput(result) {
92
+ return [result.stdout, result.stderr].filter(Boolean).join("\n");
93
+ }
94
+ function isMissingRegistryEntry(output) {
95
+ const normalized = output.toLowerCase();
96
+ return normalized.includes("e404") || normalized.includes("404") || normalized.includes("no match") || normalized.includes("no matching version") || normalized.includes("not found");
97
+ }
98
+ function parseDependencySpec(context, name, range) {
99
+ const { graph } = context;
100
+ if (range.startsWith("workspace:")) return {
101
+ range: range.slice(10),
102
+ linked: graph.get(`npm:${name}`),
103
+ protocol: "workspace"
104
+ };
105
+ if (range.startsWith("npm:")) {
106
+ const spec = range.slice(4);
107
+ const separator = spec.lastIndexOf("@");
108
+ if (separator <= 0) return;
109
+ const alias = spec.slice(0, separator);
110
+ return {
111
+ alias,
112
+ linked: graph.get(`npm:${alias}`),
113
+ range: spec.slice(separator + 1),
114
+ protocol: "npm"
115
+ };
116
+ }
117
+ return {
118
+ linked: graph.get(`npm:${name}`),
119
+ range
120
+ };
121
+ }
122
+ function formatDependencySpec(spec) {
123
+ if (spec.protocol === "workspace") return `workspace:${spec.range}`;
124
+ if (spec.protocol === "npm") return `npm:${spec.alias}@${spec.range}`;
125
+ return spec.range;
126
+ }
127
+ function npm({ client: defaultClient, onBreakPeerDep = "set", bumpDep: getBumpDepType = ({ kind }) => {
128
+ switch (kind) {
129
+ case "dependencies":
130
+ case "optionalDependencies": return "patch";
131
+ case "devDependencies": return false;
132
+ case "peerDependencies":
133
+ if (onBreakPeerDep === "ignore") return false;
134
+ return "major";
135
+ }
136
+ } } = {}) {
137
+ let client;
138
+ function depsPolicy(context) {
139
+ const { graph } = context;
140
+ function needsUpdate(dependent, spec, target) {
141
+ if (spec.linked) {
142
+ const group = graph.getPackageGroup(dependent.id);
143
+ if (group?.options.syncBump && graph.getPackageGroup(spec.linked.id) === group) return false;
144
+ if (spec.protocol === "workspace") switch (spec.range) {
145
+ case "":
146
+ case "*": return true;
147
+ case "^":
148
+ case "~": return !semver.satisfies(target, `${spec.range}${spec.linked.version}`);
149
+ }
150
+ }
151
+ if (!semver.validRange(spec.range)) return false;
152
+ return !semver.satisfies(target, spec.range);
153
+ }
154
+ return {
155
+ id: "npm:deps",
156
+ onUpdate({ pkg, plan }) {
157
+ if (!(pkg instanceof NpmPackage)) return;
158
+ for (const dependent of graph.getPackages()) {
159
+ if (!(dependent instanceof NpmPackage)) continue;
160
+ for (const field of DEP_FIELDS) {
161
+ const dependencies = dependent.manifest[field];
162
+ if (!dependencies) continue;
163
+ for (const [k, v] of Object.entries(dependencies)) {
164
+ const spec = parseDependencySpec(context, k, v);
165
+ if (!spec || spec.linked !== pkg) continue;
166
+ if (!needsUpdate(dependent, spec, plan.bumpVersion(pkg))) continue;
167
+ const bumpType = getBumpDepType({
168
+ kind: field,
169
+ spec,
170
+ name: k
171
+ });
172
+ if (bumpType === false) continue;
173
+ this.bumpPackage(dependent, {
174
+ type: bumpType,
175
+ reason: `update dependency "${k}"`
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+ };
182
+ }
183
+ return {
184
+ name: "npm",
185
+ enforce: "pre",
186
+ async init() {
187
+ if (defaultClient) {
188
+ client = defaultClient;
189
+ return;
190
+ }
191
+ if ((await detect({ cwd: this.cwd }))?.name === "pnpm") client = "pnpm";
192
+ else client = "npm";
193
+ },
194
+ async resolve() {
195
+ await discoverNpmPackages(this.cwd, (pkg) => this.graph.add(pkg));
196
+ },
197
+ createRegistryClient() {
198
+ return new NpmRegistryClient(this.cwd, client, this.graph);
199
+ },
200
+ initPlan(plan) {
201
+ plan.addPolicy(depsPolicy(this));
202
+ },
203
+ async applyPlan(draft) {
204
+ const { graph } = this;
205
+ const writes = [];
206
+ for (const pkg of graph.getPackages()) {
207
+ if (!(pkg instanceof NpmPackage)) continue;
208
+ const plan = draft.getPackagePlan(pkg.id);
209
+ if (plan) pkg.manifest.version = plan.bumpVersion(pkg);
210
+ }
211
+ for (const pkg of graph.getPackages()) {
212
+ if (!(pkg instanceof NpmPackage)) continue;
213
+ for (const field of DEP_FIELDS) {
214
+ const dependencies = pkg.manifest[field];
215
+ if (!dependencies) continue;
216
+ for (const [k, v] of Object.entries(dependencies)) {
217
+ const spec = parseDependencySpec(this, k, v);
218
+ if (!spec || !spec.linked) continue;
219
+ if (!semver.validRange(spec.range) || spec.protocol === "workspace") continue;
220
+ if (semver.satisfies(spec.linked.version, spec.range)) continue;
221
+ let updatedRange;
222
+ const isPeer = field === "peerDependencies";
223
+ if (isPeer && onBreakPeerDep === "ignore") continue;
224
+ else if (isPeer && onBreakPeerDep === "set") updatedRange = spec.linked.version;
225
+ else if (isPeer && onBreakPeerDep === "error") throw new Error(`[Tegami] the version of "${spec.linked.name}" is beyond its peer dependency constraint "${v}" in package "${pkg.name}", please update the constraint to satisfy.`);
226
+ else if (spec.range.startsWith("^")) updatedRange = `^${spec.linked.version}`;
227
+ else if (spec.range.startsWith("~")) updatedRange = `~${spec.linked.version}`;
228
+ else updatedRange = spec.linked.version;
229
+ dependencies[k] = formatDependencySpec({
230
+ ...spec,
231
+ range: updatedRange
232
+ });
233
+ }
234
+ }
235
+ writes.push(pkg.write());
236
+ }
237
+ await Promise.all(writes);
238
+ }
239
+ };
240
+ }
241
+ async function discoverNpmPackages(cwd, add) {
242
+ let patterns;
243
+ const rootManifest = await readManifest(cwd).catch(() => void 0);
244
+ const pnpmPatterns = await readFile(join(cwd, "pnpm-workspace.yaml"), "utf8").then((content) => pnpmWorkspaceSchema.parse(load(content) ?? {})).catch((error) => {
245
+ if (isNodeError(error) && error.code === "ENOENT") return void 0;
246
+ throw error;
247
+ });
248
+ if (pnpmPatterns) patterns = pnpmPatterns.packages ?? [];
249
+ else patterns = rootManifest?.workspaces ?? [];
250
+ const candidatePaths = await expandWorkspacePatterns(cwd, patterns);
251
+ const manifests = await Promise.all(candidatePaths.map((path) => readManifest(path).then((manifest) => ({
252
+ path,
253
+ manifest
254
+ })).catch(() => void 0)));
255
+ if (rootManifest) add(new NpmPackage(cwd, rootManifest));
256
+ for (const entry of manifests) {
257
+ if (!entry?.manifest) continue;
258
+ add(new NpmPackage(entry.path, entry.manifest));
259
+ }
260
+ }
261
+ async function expandWorkspacePatterns(cwd, patterns) {
262
+ if (patterns.length === 0) return [];
263
+ return glob(patterns, {
264
+ absolute: true,
265
+ cwd,
266
+ ignore: ["**/node_modules/**", "**/dist/**"],
267
+ onlyDirectories: true,
268
+ onlyFiles: false
269
+ });
270
+ }
271
+ async function readManifest(packagePath) {
272
+ const content = await readFile(join(packagePath, "package.json"), "utf8");
273
+ const parsed = JSON.parse(content);
274
+ packageManifestSchema.parse(parsed);
275
+ return parsed;
276
+ }
277
+ //#endregion
278
+ export { NpmPackage, NpmRegistryClient, npm };
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ //#region src/schemas.ts
3
+ const changelogFrontmatterSchema = z.object({
4
+ subject: z.string().optional(),
5
+ packages: z.array(z.string()).default([])
6
+ });
7
+ const stringRecordSchema = z.record(z.string(), z.string());
8
+ const jsonCodec = (schema) => z.codec(z.string(), schema, {
9
+ decode: (jsonString, ctx) => {
10
+ try {
11
+ return JSON.parse(jsonString);
12
+ } catch (err) {
13
+ ctx.issues.push({
14
+ code: "invalid_format",
15
+ format: "json",
16
+ input: jsonString,
17
+ message: err.message
18
+ });
19
+ return z.NEVER;
20
+ }
21
+ },
22
+ encode: (value) => JSON.stringify(value)
23
+ });
24
+ const pnpmWorkspaceSchema = z.looseObject({ packages: z.array(z.string()).optional() });
25
+ const packageManifestSchema = z.looseObject({
26
+ name: z.string(),
27
+ version: z.string().optional(),
28
+ private: z.boolean().optional(),
29
+ publishConfig: z.looseObject({
30
+ access: z.enum(["public", "restricted"]).optional(),
31
+ registry: z.string().optional(),
32
+ tag: z.string().optional()
33
+ }).optional(),
34
+ workspaces: z.array(z.string()).optional(),
35
+ dependencies: stringRecordSchema.optional(),
36
+ devDependencies: stringRecordSchema.optional(),
37
+ peerDependencies: stringRecordSchema.optional(),
38
+ optionalDependencies: stringRecordSchema.optional()
39
+ });
40
+ //#endregion
41
+ export { pnpmWorkspaceSchema as i, jsonCodec as n, packageManifestSchema as r, changelogFrontmatterSchema as t };
@@ -0,0 +1,33 @@
1
+ import { inc, parse } from "semver";
2
+ //#region src/utils/semver.ts
3
+ function formatNpmDistTag(distTag) {
4
+ return distTag && distTag !== "latest" ? ` (${distTag})` : "";
5
+ }
6
+ function formatPackageVersion(name, version, distTag) {
7
+ return `${name}@${version}${formatNpmDistTag(distTag)}`;
8
+ }
9
+ const WEIGHTS = {
10
+ major: 3,
11
+ minor: 2,
12
+ patch: 1
13
+ };
14
+ const PRE = {
15
+ major: "premajor",
16
+ minor: "preminor",
17
+ patch: "prepatch"
18
+ };
19
+ function maxBump(a, b) {
20
+ if (WEIGHTS[a] > WEIGHTS[b]) return a;
21
+ return b;
22
+ }
23
+ function bumpVersion(version, type, prerelease) {
24
+ let next = version;
25
+ if (prerelease) {
26
+ if (parse(version)?.prerelease[0] === prerelease) next = inc(version, "prerelease", prerelease);
27
+ else if (type) next = inc(version, PRE[type], prerelease);
28
+ } else if (type) next = inc(version, type);
29
+ if (!next) throw new Error(`Invalid semver version: ${version}`);
30
+ return next;
31
+ }
32
+ //#endregion
33
+ export { maxBump as i, formatNpmDistTag as n, formatPackageVersion as r, bumpVersion as t };
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "name": "tegami",
3
- "version": "0.0.1",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Utility for package versioning & publish",
5
+ "license": "MIT",
6
+ "author": "Fuma Nama",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "github:fuma-nama/tegami"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": "./dist/index.js",
17
+ "./cli": "./dist/cli/index.js",
18
+ "./generators/simple": "./dist/generators/simple.js",
19
+ "./plugins/git": "./dist/plugins/git.js",
20
+ "./plugins/github": "./dist/plugins/github.js",
21
+ "./providers/cargo": "./dist/providers/cargo.js",
22
+ "./providers/npm": "./dist/providers/npm.js",
23
+ "./package.json": "./package.json"
24
+ },
4
25
  "publishConfig": {
5
26
  "access": "public"
6
27
  },
@@ -26,27 +47,6 @@
26
47
  "typescript": "6.0.3",
27
48
  "@repo/typescript-config": "0.0.0"
28
49
  },
29
- "description": "Utility for package versioning & publish",
30
- "license": "MIT",
31
- "author": "Fuma Nama",
32
- "repository": {
33
- "type": "git",
34
- "url": "github:fuma-nama/tegami"
35
- },
36
- "files": [
37
- "dist"
38
- ],
39
- "type": "module",
40
- "exports": {
41
- ".": "./dist/index.mjs",
42
- "./cli": "./dist/cli/index.mjs",
43
- "./generators/simple": "./dist/generators/simple.mjs",
44
- "./plugins/git": "./dist/plugins/git.mjs",
45
- "./plugins/github": "./dist/plugins/github.mjs",
46
- "./providers/cargo": "./dist/providers/cargo.mjs",
47
- "./providers/npm": "./dist/providers/npm.mjs",
48
- "./package.json": "./package.json"
49
- },
50
50
  "scripts": {
51
51
  "types:check": "tsc --noEmit",
52
52
  "build": "tsdown",