@dbx-tools/projen 0.9.18 → 0.9.21

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.
Files changed (105) hide show
  1. package/._LICENSE +0 -0
  2. package/._README.md +0 -0
  3. package/._index.ts +0 -0
  4. package/._package.json +0 -0
  5. package/README.md +72 -88
  6. package/index.ts +2 -7
  7. package/package.json +9 -5
  8. package/src/.__lazy-require.ts +0 -0
  9. package/src/.__release-platform.ts +0 -0
  10. package/src/.__rust-project.ts +0 -0
  11. package/src/.__rust-release-workflow.ts +0 -0
  12. package/src/.__task-command.ts +0 -0
  13. package/src/._barrels.ts +0 -0
  14. package/src/._bun-app.ts +0 -0
  15. package/src/._bun-workflow.ts +0 -0
  16. package/src/._clean.ts +0 -0
  17. package/src/._codegen.ts +0 -0
  18. package/src/._dbx-tools-config.ts +0 -0
  19. package/src/._engine-root.ts +0 -0
  20. package/src/._generated.ts +0 -0
  21. package/src/._mixin.ts +0 -0
  22. package/src/._module-exports.ts +0 -0
  23. package/src/._openapi.ts +0 -0
  24. package/src/._packages.ts +0 -0
  25. package/src/._pnpm-workspace.ts +0 -0
  26. package/src/._project-js.ts +0 -0
  27. package/src/._project-predicate.ts +0 -0
  28. package/src/._project-py.ts +0 -0
  29. package/src/._project-rs.ts +0 -0
  30. package/src/._project.ts +0 -0
  31. package/src/._projen-version.ts +0 -0
  32. package/src/._publish.ts +0 -0
  33. package/src/._release-catalog.ts +0 -0
  34. package/src/._release-dispatch.ts +0 -0
  35. package/src/._release-github.ts +0 -0
  36. package/src/._release.ts +0 -0
  37. package/src/._scaffold.ts +0 -0
  38. package/src/._tags.ts +0 -0
  39. package/src/._tsconfig.ts +0 -0
  40. package/src/._uniffi.ts +0 -0
  41. package/src/._vscode.ts +0 -0
  42. package/src/._watch.ts +0 -0
  43. package/src/._workspace-lock.ts +0 -0
  44. package/src/._workspace-version.ts +0 -0
  45. package/src/_rust-release-workflow.ts +18 -582
  46. package/src/_task-command.ts +75 -0
  47. package/src/project-js.ts +2 -42
  48. package/src/project-py.ts +60 -162
  49. package/src/project-rs.ts +4 -100
  50. package/src/release-catalog.ts +37 -203
  51. package/src/release.ts +96 -775
  52. package/src/scaffold.ts +2 -6
  53. package/src/workspace-version.ts +9 -121
  54. package/tasks/._barrels.ts +0 -0
  55. package/tasks/._bump.ts +0 -0
  56. package/tasks/._clean.ts +0 -0
  57. package/tasks/._compile-workspace.ts +0 -0
  58. package/tasks/._local-publish.ts +0 -0
  59. package/tasks/._openapi.ts +0 -0
  60. package/tasks/._projenrc.ts +0 -0
  61. package/tasks/._publish-npm.ts +0 -0
  62. package/tasks/._publish-python.ts +0 -0
  63. package/tasks/._publish-uniffi-local.ts +0 -0
  64. package/tasks/._publish.ts +0 -0
  65. package/tasks/._python-registry.ts +0 -0
  66. package/tasks/._release-assets.ts +0 -0
  67. package/tasks/._release-candidate.ts +0 -0
  68. package/tasks/._release-manifest.ts +0 -0
  69. package/tasks/._release-pr.ts +0 -0
  70. package/tasks/._release-summary.ts +0 -0
  71. package/tasks/._release-version.ts +0 -0
  72. package/tasks/._rust.ts +0 -0
  73. package/tasks/._sync.ts +0 -0
  74. package/tasks/._uniffi-python.d.ts +0 -0
  75. package/tasks/._uniffi-python.js +0 -0
  76. package/tasks/._uniffi-release.mjs +0 -0
  77. package/tasks/._uniffi.ts +0 -0
  78. package/tasks/._version-check.ts +0 -0
  79. package/tasks/bump.ts +17 -34
  80. package/tasks/local-publish.ts +124 -117
  81. package/tasks/publish-npm.ts +125 -41
  82. package/tasks/publish-python.ts +79 -106
  83. package/tasks/publish-uniffi-local.ts +129 -84
  84. package/tasks/publish.ts +44 -139
  85. package/tasks/python-registry.ts +2 -9
  86. package/tasks/release-assets.ts +56 -65
  87. package/tasks/release-candidate.ts +416 -0
  88. package/tasks/release-manifest.ts +273 -0
  89. package/tasks/release-pr.ts +312 -369
  90. package/tasks/release-summary.ts +8 -10
  91. package/tasks/release-version.ts +23 -2
  92. package/tasks/rust.ts +8 -13
  93. package/tasks/uniffi-python.d.ts +2 -3
  94. package/tasks/uniffi-python.js +7 -20
  95. package/tasks/uniffi-release.mjs +19 -13
  96. package/tasks/uniffi.ts +15 -24
  97. package/tasks/version-check.ts +24 -56
  98. package/src/release-plan.ts +0 -269
  99. package/src/release-please.ts +0 -242
  100. package/tasks/release-bootstrap.ts +0 -59
  101. package/tasks/release-plan.ts +0 -89
  102. package/tasks/release-please.ts +0 -162
  103. package/tasks/release-request.ts +0 -166
  104. package/tasks/release-summary-units.ts +0 -75
  105. package/tasks/stamp-python.ts +0 -13
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env -S bun
2
+ /** Create, verify, and select immutable release-candidate assets. */
3
+
4
+ import { createHash } from "node:crypto";
5
+ import {
6
+ copyFileSync,
7
+ existsSync,
8
+ mkdirSync,
9
+ readdirSync,
10
+ readFileSync,
11
+ rmSync,
12
+ statSync,
13
+ writeFileSync,
14
+ } from "node:fs";
15
+ import { basename, join, resolve } from "node:path";
16
+ import { parseArgs } from "node:util";
17
+
18
+ export type ReleaseArtifactKind = "binary" | "npm" | "pypi";
19
+ export type ReleaseArtifactRole = "facade" | "native" | "workspace";
20
+
21
+ export interface ReleaseArtifactInput {
22
+ readonly kind: ReleaseArtifactKind;
23
+ readonly packageName?: string;
24
+ readonly packageVersion?: string;
25
+ readonly path: string;
26
+ readonly role?: ReleaseArtifactRole;
27
+ }
28
+
29
+ export interface ReleaseArtifact {
30
+ readonly kind: ReleaseArtifactKind;
31
+ readonly name: string;
32
+ readonly packageName?: string;
33
+ readonly packageVersion?: string;
34
+ readonly role?: ReleaseArtifactRole;
35
+ readonly sha256: string;
36
+ readonly size: number;
37
+ }
38
+
39
+ export interface ReleaseManifest {
40
+ readonly schemaVersion: 1;
41
+ readonly tag: string;
42
+ readonly version: string;
43
+ readonly gitSha: string;
44
+ readonly artifacts: readonly ReleaseArtifact[];
45
+ }
46
+
47
+ function sha256(path: string): string {
48
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
49
+ }
50
+
51
+ /** Copy release artifacts into one upload directory and write its trust manifest. */
52
+ export function writeReleaseManifest(options: {
53
+ readonly artifacts: readonly ReleaseArtifactInput[];
54
+ readonly directory: string;
55
+ readonly gitSha: string;
56
+ readonly tag: string;
57
+ readonly version: string;
58
+ }): ReleaseManifest {
59
+ const directory = resolve(options.directory);
60
+ rmSync(directory, { recursive: true, force: true });
61
+ mkdirSync(directory, { recursive: true });
62
+ const names = new Set<string>();
63
+ const artifacts = options.artifacts
64
+ .map((artifact): ReleaseArtifact => {
65
+ const source = resolve(artifact.path);
66
+ if (!existsSync(source)) throw new Error(`Missing release artifact: ${source}`);
67
+ const name = basename(source);
68
+ if (names.has(name)) throw new Error(`Duplicate release artifact name: ${name}`);
69
+ names.add(name);
70
+ copyFileSync(source, join(directory, name));
71
+ return {
72
+ kind: artifact.kind,
73
+ name,
74
+ ...(artifact.packageName ? { packageName: artifact.packageName } : {}),
75
+ ...(artifact.packageVersion ? { packageVersion: artifact.packageVersion } : {}),
76
+ ...(artifact.role ? { role: artifact.role } : {}),
77
+ sha256: sha256(source),
78
+ size: statSync(source).size,
79
+ };
80
+ })
81
+ .sort((left, right) => left.name.localeCompare(right.name));
82
+ const manifest: ReleaseManifest = {
83
+ schemaVersion: 1,
84
+ tag: options.tag,
85
+ version: options.version,
86
+ gitSha: options.gitSha,
87
+ artifacts,
88
+ };
89
+ writeFileSync(join(directory, "release-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
90
+ writeFileSync(
91
+ join(directory, "SHA256SUMS"),
92
+ `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`,
93
+ );
94
+ return manifest;
95
+ }
96
+
97
+ function readManifest(directory: string): ReleaseManifest {
98
+ const value = JSON.parse(
99
+ readFileSync(join(directory, "release-manifest.json"), "utf8"),
100
+ ) as unknown;
101
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
102
+ throw new Error("Unsupported release manifest");
103
+ }
104
+ const record = value as Record<string, unknown>;
105
+ if (
106
+ record.schemaVersion !== 1 ||
107
+ typeof record.tag !== "string" ||
108
+ typeof record.version !== "string" ||
109
+ typeof record.gitSha !== "string" ||
110
+ !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(record.gitSha) ||
111
+ !Array.isArray(record.artifacts)
112
+ ) {
113
+ throw new Error("Unsupported release manifest");
114
+ }
115
+ const names = new Set<string>();
116
+ const artifacts = record.artifacts.map((value): ReleaseArtifact => {
117
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
118
+ throw new Error("Invalid release artifact");
119
+ }
120
+ const artifact = value as Record<string, unknown>;
121
+ if (
122
+ !["binary", "npm", "pypi"].includes(String(artifact.kind)) ||
123
+ typeof artifact.name !== "string" ||
124
+ basename(artifact.name) !== artifact.name ||
125
+ artifact.name === "." ||
126
+ artifact.name === ".." ||
127
+ typeof artifact.sha256 !== "string" ||
128
+ !/^[0-9a-f]{64}$/.test(artifact.sha256) ||
129
+ !Number.isSafeInteger(artifact.size) ||
130
+ Number(artifact.size) < 0
131
+ ) {
132
+ throw new Error("Invalid release artifact");
133
+ }
134
+ if (names.has(artifact.name)) {
135
+ throw new Error(`Duplicate release artifact name: ${artifact.name}`);
136
+ }
137
+ names.add(artifact.name);
138
+ const kind = artifact.kind as ReleaseArtifactKind;
139
+ const packageName = artifact.packageName;
140
+ const packageVersion = artifact.packageVersion;
141
+ const role = artifact.role;
142
+ if (
143
+ (packageName !== undefined && typeof packageName !== "string") ||
144
+ (packageVersion !== undefined && typeof packageVersion !== "string") ||
145
+ (role !== undefined && !["facade", "native", "workspace"].includes(String(role))) ||
146
+ (kind !== "binary" && (!packageName || !packageVersion))
147
+ ) {
148
+ throw new Error(`Invalid release artifact metadata: ${artifact.name}`);
149
+ }
150
+ return {
151
+ kind,
152
+ name: artifact.name,
153
+ ...(packageName ? { packageName } : {}),
154
+ ...(packageVersion ? { packageVersion } : {}),
155
+ ...(role ? { role: role as ReleaseArtifactRole } : {}),
156
+ sha256: artifact.sha256,
157
+ size: Number(artifact.size),
158
+ };
159
+ });
160
+ return {
161
+ schemaVersion: 1,
162
+ tag: record.tag,
163
+ version: record.version,
164
+ gitSha: record.gitSha,
165
+ artifacts,
166
+ };
167
+ }
168
+
169
+ /** Verify a release bundle and optionally extract one registry/package subset. */
170
+ export function verifyReleaseManifest(options: {
171
+ readonly directory: string;
172
+ readonly gitSha: string;
173
+ readonly kind?: ReleaseArtifactKind;
174
+ readonly output?: string;
175
+ readonly packageName?: string;
176
+ readonly tag: string;
177
+ readonly version: string;
178
+ }): readonly ReleaseArtifact[] {
179
+ const directory = resolve(options.directory);
180
+ const manifest = readManifest(directory);
181
+ if (manifest.tag !== options.tag) {
182
+ throw new Error(`Release manifest tag ${manifest.tag} does not match ${options.tag}`);
183
+ }
184
+ if (manifest.version !== options.version) {
185
+ throw new Error(
186
+ `Release manifest version ${manifest.version} does not match ${options.version}`,
187
+ );
188
+ }
189
+ if (manifest.gitSha !== options.gitSha) {
190
+ throw new Error(`Release manifest SHA ${manifest.gitSha} does not match ${options.gitSha}`);
191
+ }
192
+ for (const artifact of manifest.artifacts) {
193
+ if (artifact.kind !== "binary" && artifact.packageVersion !== options.version) {
194
+ throw new Error(
195
+ `Release artifact ${artifact.name} carries ${String(artifact.packageVersion)}, expected ${options.version}`,
196
+ );
197
+ }
198
+ }
199
+ const expectedChecksums = `${manifest.artifacts
200
+ .map((artifact) => `${artifact.sha256} ${artifact.name}`)
201
+ .join("\n")}\n`;
202
+ const checksums = readFileSync(join(directory, "SHA256SUMS"), "utf8");
203
+ if (checksums !== expectedChecksums) throw new Error("SHA256SUMS does not match the manifest");
204
+ const selected = manifest.artifacts.filter(
205
+ (artifact) =>
206
+ (!options.kind || artifact.kind === options.kind) &&
207
+ (!options.packageName || artifact.packageName === options.packageName),
208
+ );
209
+ if ((options.kind || options.packageName) && selected.length === 0) {
210
+ throw new Error("Release manifest selection matched no artifacts");
211
+ }
212
+ if (!options.kind && !options.packageName) {
213
+ const expected = new Set([
214
+ "release-manifest.json",
215
+ "SHA256SUMS",
216
+ ...manifest.artifacts.map((artifact) => artifact.name),
217
+ ]);
218
+ const present = readdirSync(directory).filter((name) => !name.startsWith("."));
219
+ const unexpected = present.filter((name) => !expected.has(name));
220
+ if (unexpected.length > 0) {
221
+ throw new Error(`Release contains unmanifested assets: ${unexpected.sort().join(", ")}`);
222
+ }
223
+ }
224
+ const output = options.output ? resolve(options.output) : undefined;
225
+ if (output) {
226
+ rmSync(output, { recursive: true, force: true });
227
+ mkdirSync(output, { recursive: true });
228
+ }
229
+ for (const artifact of selected) {
230
+ const source = join(directory, artifact.name);
231
+ if (!existsSync(source)) throw new Error(`Missing release asset: ${artifact.name}`);
232
+ if (statSync(source).size !== artifact.size) {
233
+ throw new Error(`Release asset size does not match: ${artifact.name}`);
234
+ }
235
+ if (sha256(source) !== artifact.sha256) {
236
+ throw new Error(`Release asset checksum does not match: ${artifact.name}`);
237
+ }
238
+ if (output) copyFileSync(source, join(output, artifact.name));
239
+ }
240
+ return selected;
241
+ }
242
+
243
+ if (import.meta.main) {
244
+ const { positionals, values } = parseArgs({
245
+ args: process.argv.slice(2),
246
+ allowPositionals: true,
247
+ options: {
248
+ directory: { type: "string" },
249
+ kind: { type: "string" },
250
+ output: { type: "string" },
251
+ package: { type: "string" },
252
+ sha: { type: "string" },
253
+ tag: { type: "string" },
254
+ version: { type: "string" },
255
+ },
256
+ });
257
+ if (positionals[0] !== "verify") throw new Error("Expected verify command");
258
+ if (!values.directory || !values.sha || !values.tag || !values.version) {
259
+ throw new Error("verify requires --directory, --sha, --tag, and --version");
260
+ }
261
+ if (values.kind && !["binary", "npm", "pypi"].includes(values.kind)) {
262
+ throw new Error(`Unknown release artifact kind: ${values.kind}`);
263
+ }
264
+ verifyReleaseManifest({
265
+ directory: values.directory,
266
+ gitSha: values.sha,
267
+ ...(values.kind ? { kind: values.kind as ReleaseArtifactKind } : {}),
268
+ ...(values.output ? { output: values.output } : {}),
269
+ ...(values.package ? { packageName: values.package } : {}),
270
+ tag: values.tag,
271
+ version: values.version,
272
+ });
273
+ }