@tallyrelease/plugins 0.5.0 → 0.6.0-beta.12

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.
@@ -1,3 +1 @@
1
- export declare const dartPlugin: import("@tallyrelease/core").Plugin<{
2
- buildOffset?: number | undefined;
3
- }>;
1
+ export declare const dartPlugin: import("@tallyrelease/core").Plugin<Record<string, never>>;
@@ -1,18 +1,21 @@
1
- import { defaultUnitId, definePlugin, flutter } from "@tallyrelease/core";
1
+ import { defaultUnitId, definePlugin } from "@tallyrelease/core";
2
2
  import { parse as parseYaml } from "yaml";
3
3
  import { z } from "zod";
4
4
  import { edgeBetween, unitsByPackage } from "../_kit/edges.js";
5
5
  import { joinRoot, rootOf } from "../_kit/paths.js";
6
- const optionsSchema = z.strictObject({
7
- /** Added to the first-parent commit count for flutter build numbers. */
8
- buildOffset: z.number().int().nonnegative().optional(),
9
- });
6
+ import { BUILD, NUMBERS, PRERELEASE } from "../_kit/semver.js";
7
+ const optionsSchema = z.strictObject({});
10
8
  const PUBSPEC = "pubspec.yaml";
9
+ const MELOS = "melos.yaml";
11
10
  const DEP_FIELDS = [
12
11
  "dependencies",
13
12
  "dev_dependencies",
14
13
  "dependency_overrides",
15
14
  ];
15
+ const VERSION = `${NUMBERS}${PRERELEASE}${BUILD}`;
16
+ const EXACT = new RegExp(`^${VERSION}$`);
17
+ /** An exact `name: version` entry; the groups are the indent, name, colon, quote and trailing text. */
18
+ const PIN_LINE = new RegExp(`^([ \\t]+)([\\w.-]+)(:[ \\t]*)(["']?)${VERSION}\\4(?=\\s|$)(.*)$`);
16
19
  /** The top-level `version:` line; group 3 is the value without quotes. */
17
20
  const VERSION_LINE = /^(version:[ \t]*)(["']?)([^\s"'#]+)\2/m;
18
21
  const record = (value) => typeof value === "object" && value !== null
@@ -34,49 +37,95 @@ const readPubspec = (ctx, root) => {
34
37
  };
35
38
  /** Directories whose pubspecs are never units. */
36
39
  const SKIPPED = new Set(["build", "example"]);
37
- /** Roots of every `pubspec.yaml`, skipping dot, `build` and `example` directories. */
38
- const pubspecRoots = (ctx) => ctx.fs
39
- .glob([PUBSPEC, `**/${PUBSPEC}`])
40
- .filter((p) => p
41
- .split("/")
42
- .slice(0, -1)
43
- .every((s) => !s.startsWith(".") && !SKIPPED.has(s)))
44
- .map((p) => (p === PUBSPEC ? "." : rootOf(p, PUBSPEC)));
45
- /** A private app that depends on the Flutter SDK. */
46
- const isFlutterApp = (pubspec) => pubspec["publish_to"] === "none" &&
47
- (record(record(pubspec["dependencies"])["flutter"])["sdk"] === "flutter" ||
48
- record(pubspec["environment"])["flutter"] !== undefined);
40
+ /** A root pubspec that only anchors a pub workspace or melos setup, without its own version. */
41
+ const isWorkspaceRoot = (ctx) => {
42
+ const pubspec = readPubspec(ctx, ".");
43
+ return (pubspec !== undefined &&
44
+ pubspec["version"] === undefined &&
45
+ (pubspec["workspace"] !== undefined || ctx.fs.exists(MELOS)));
46
+ };
47
+ /** Roots of every `pubspec.yaml`, skipping dot, `build` and `example` directories and a workspace root. */
48
+ const pubspecRoots = (ctx) => {
49
+ const skipRoot = isWorkspaceRoot(ctx);
50
+ return ctx.fs
51
+ .glob([PUBSPEC, `**/${PUBSPEC}`])
52
+ .filter((p) => p
53
+ .split("/")
54
+ .slice(0, -1)
55
+ .every((s) => !s.startsWith(".") && !SKIPPED.has(s)))
56
+ .map((p) => (p === PUBSPEC ? "." : rootOf(p, PUBSPEC)))
57
+ .filter((root) => !(skipRoot && root === "."));
58
+ };
59
+ /**
60
+ * Whether a dependency entry makes the dependent ship the dependency's code:
61
+ * `path:` maps and exact versions under `dependencies`. Undefined when the
62
+ * entry is no link to a unit.
63
+ */
64
+ const pinnedLink = (field, spec) => {
65
+ if (spec === null) {
66
+ return false;
67
+ }
68
+ if (typeof spec === "string") {
69
+ return field === "dependencies" && EXACT.test(spec);
70
+ }
71
+ return typeof record(spec)["path"] === "string"
72
+ ? field === "dependencies"
73
+ : undefined;
74
+ };
75
+ /** Sets the exact version entries under top-level `dependencies:` that name a released package. */
76
+ const rewritePins = (content, released) => {
77
+ let inDeps = false;
78
+ let indent;
79
+ return content.replace(/^.*$/gm, (line) => {
80
+ if (/^dependencies:\s*(#.*)?$/.test(line)) {
81
+ inDeps = true;
82
+ indent = undefined;
83
+ return line;
84
+ }
85
+ if (!inDeps || /^\s*(#.*)?$/.test(line)) {
86
+ return line;
87
+ }
88
+ if (!/^\s/.test(line)) {
89
+ inDeps = false;
90
+ return line;
91
+ }
92
+ const [, lead = "", name = "", colon = "", quote = "", rest = ""] = PIN_LINE.exec(line) ?? [];
93
+ const version = released.get(name);
94
+ indent ??= /^[ \t]*/.exec(line)?.[0];
95
+ if (version === undefined || lead !== indent) {
96
+ return line;
97
+ }
98
+ return `${lead}${name}${colon}${quote}${version}${quote}${rest}`;
99
+ });
100
+ };
49
101
  export const dartPlugin = definePlugin({
50
102
  apiVersion: 1,
51
103
  name: "dart",
52
104
  options: optionsSchema,
53
- schemes: { flutter },
54
105
  discover: (ctx) => pubspecRoots(ctx).map((root) => {
55
106
  const pubspec = readPubspec(ctx, root) ?? {};
56
107
  const packageName = pubspec["name"];
57
- const { buildOffset } = ctx.options;
58
108
  return {
59
109
  id: defaultUnitId(root, ctx.repo.name),
60
110
  root,
61
111
  version: "file",
62
- scheme: isFlutterApp(pubspec) ? "flutter" : "semver",
63
- meta: {
64
- ...(typeof packageName === "string" && { packageName }),
65
- ...(buildOffset !== undefined && { buildOffset }),
66
- },
112
+ meta: typeof packageName === "string" ? { packageName } : {},
67
113
  };
68
114
  }),
69
115
  edges: (ctx, units) => {
70
- const byName = unitsByPackage(units, "dart");
116
+ const byName = unitsByPackage(units, "dart", (name, prior, unit) => {
117
+ ctx.log.warn(`package "${name}" is declared by "${prior.root}" and "${unit.root}"; using "${unit.root}"`);
118
+ });
71
119
  const edges = [];
72
120
  for (const to of byName.values()) {
73
121
  const pubspec = readPubspec(ctx, to.root) ?? {};
74
122
  for (const field of DEP_FIELDS) {
75
123
  for (const [name, spec] of Object.entries(record(pubspec[field]))) {
76
- if (typeof record(spec)["path"] === "string") {
124
+ const pinned = pinnedLink(field, spec);
125
+ if (pinned !== undefined) {
77
126
  edges.push(...edgeBetween(byName.get(name), to, {
78
127
  reason: `${field}: ${name}`,
79
- pinned: false,
128
+ pinned,
80
129
  }));
81
130
  }
82
131
  }
@@ -88,11 +137,29 @@ export const dartPlugin = definePlugin({
88
137
  {
89
138
  path: joinRoot(unit.root, PUBSPEC),
90
139
  read: (content) => VERSION_LINE.exec(content)?.[3],
91
- // A pubspec without a version line gets one after `name:`.
92
- write: (content, next) => VERSION_LINE.test(content)
93
- ? content.replace(VERSION_LINE, (_, key, quote) => `${key}${quote}${next}${quote}`)
94
- : content.replace(/^name:.*$/m, (line) => `${line}\nversion: ${next}`),
140
+ write: (content, next, { units, versions }) => {
141
+ const released = new Map();
142
+ for (const u of units) {
143
+ const name = u.meta["dart"]?.["packageName"];
144
+ const version = versions.get(u.id);
145
+ if (typeof name === "string" && version !== undefined) {
146
+ released.set(name, version);
147
+ }
148
+ }
149
+ // A pubspec without a version line gets one after `name:`.
150
+ const versioned = VERSION_LINE.test(content)
151
+ ? content.replace(VERSION_LINE, (_, key, quote) => `${key}${quote}${next}${quote}`)
152
+ : content.replace(/^name:.*$/m, (line) => `${line}${content.includes("\r\n") ? "\r\n" : "\n"}version: ${next}`);
153
+ return rewritePins(versioned, released);
154
+ },
95
155
  },
96
156
  ],
97
- inputs: () => ({ global: ["pubspec.lock"] }),
157
+ inputs: (ctx) => ({
158
+ global: [
159
+ "pubspec.lock",
160
+ MELOS,
161
+ "analysis_options.yaml",
162
+ ...(isWorkspaceRoot(ctx) ? [PUBSPEC] : []),
163
+ ],
164
+ }),
98
165
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyrelease/plugins",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-beta.12",
4
4
  "description": "Built-in ecosystem plugins of tallyrelease",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "prettier": "@abinnovision/prettier-config",
43
43
  "dependencies": {
44
- "@tallyrelease/core": "^0.5.0",
44
+ "@tallyrelease/core": "^0.6.0-beta.12",
45
45
  "yaml": "^2.9.1",
46
46
  "zod": "^4.6.7"
47
47
  },