@dbx-tools/projen 0.9.19 → 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 (104) 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 +67 -83
  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 +4 -391
  46. package/src/_task-command.ts +75 -0
  47. package/src/project-js.ts +2 -42
  48. package/src/project-py.ts +5 -96
  49. package/src/project-rs.ts +3 -76
  50. package/src/release-catalog.ts +37 -203
  51. package/src/release.ts +12 -634
  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 +62 -30
  82. package/tasks/publish-python.ts +38 -140
  83. package/tasks/publish-uniffi-local.ts +129 -84
  84. package/tasks/publish.ts +25 -139
  85. package/tasks/python-registry.ts +2 -9
  86. package/tasks/release-assets.ts +56 -65
  87. package/tasks/release-candidate.ts +123 -90
  88. package/tasks/release-pr.ts +266 -240
  89. package/tasks/release-summary.ts +8 -10
  90. package/tasks/release-version.ts +23 -2
  91. package/tasks/rust.ts +8 -13
  92. package/tasks/uniffi-python.d.ts +2 -3
  93. package/tasks/uniffi-python.js +7 -20
  94. package/tasks/uniffi-release.mjs +19 -13
  95. package/tasks/uniffi.ts +15 -24
  96. package/tasks/version-check.ts +24 -56
  97. package/src/release-plan.ts +0 -269
  98. package/src/release-please.ts +0 -242
  99. package/tasks/release-bootstrap.ts +0 -59
  100. package/tasks/release-plan.ts +0 -89
  101. package/tasks/release-please.ts +0 -162
  102. package/tasks/release-request.ts +0 -166
  103. package/tasks/release-summary-units.ts +0 -75
  104. package/tasks/stamp-python.ts +0 -13
@@ -2,6 +2,7 @@
2
2
  /** Idempotent npm archive publication for release recovery. */
3
3
  import { createHash } from "node:crypto";
4
4
  import {
5
+ chmodSync,
5
6
  lstatSync,
6
7
  mkdtempSync,
7
8
  readFileSync,
@@ -15,6 +16,7 @@ import { join, relative, resolve } from "node:path";
15
16
  import * as exec from "@dbx-tools/core/exec";
16
17
  import { log } from "@dbx-tools/shared-core";
17
18
  import { Command } from "commander";
19
+ import { runTaskCommand } from "../src/_task-command.ts";
18
20
 
19
21
  const DEFAULT_REGISTRY = "https://registry.npmjs.org";
20
22
  const logger = log.logger("projen:publish-npm");
@@ -29,6 +31,7 @@ export interface NpmReleaseIdentity {
29
31
  }
30
32
 
31
33
  interface NpmArchiveManifest {
34
+ readonly [key: string]: unknown;
32
35
  readonly dependencies?: Readonly<Record<string, string>>;
33
36
  readonly optionalDependencies?: Readonly<Record<string, string>>;
34
37
  readonly name?: string;
@@ -37,6 +40,10 @@ interface NpmArchiveManifest {
37
40
  readonly version?: string;
38
41
  }
39
42
 
43
+ export type NpmArchiveManifestTransform = (
44
+ manifest: NpmArchiveManifest,
45
+ ) => Readonly<Record<string, unknown>>;
46
+
40
47
  function normalizedRepository(value: unknown): string | undefined {
41
48
  const url =
42
49
  typeof value === "string"
@@ -241,25 +248,60 @@ export function packNpmPackage(
241
248
  directory: string,
242
249
  destination: string,
243
250
  path = process.env.PATH ?? "",
251
+ transformManifest?: NpmArchiveManifestTransform,
244
252
  ): string {
245
253
  const executable = process.versions.bun ? process.execPath : "bun";
246
- exec.spawnSync(
247
- executable,
248
- ["pm", "pack", "--destination", destination, "--ignore-scripts", "--quiet"],
249
- {
250
- cwd: directory,
251
- env: { ...process.env, PATH: path },
252
- stdout: "inherit",
253
- stderr: "inherit",
254
- stdin: "ignore",
255
- check: true,
256
- },
257
- );
254
+ const manifest = join(directory, "package.json");
255
+ const manifestMode = lstatSync(manifest).mode & 0o777;
256
+ const restoreManifestMode = (manifestMode & 0o200) === 0;
257
+ if (restoreManifestMode) chmodSync(manifest, manifestMode | 0o200);
258
+ try {
259
+ exec.spawnSync(
260
+ executable,
261
+ ["pm", "pack", "--destination", destination, "--ignore-scripts", "--quiet"],
262
+ {
263
+ cwd: directory,
264
+ env: { ...process.env, PATH: path },
265
+ stdout: "inherit",
266
+ stderr: "inherit",
267
+ stdin: "ignore",
268
+ check: true,
269
+ },
270
+ );
271
+ } finally {
272
+ if (restoreManifestMode) chmodSync(manifest, manifestMode);
273
+ }
258
274
  const archives = readdirSync(destination).filter((file) => file.endsWith(".tgz"));
259
275
  if (archives.length !== 1) {
260
276
  throw new Error(`Expected one packed npm archive, found ${archives.length}`);
261
277
  }
262
- return join(destination, archives[0]);
278
+ const archive = join(destination, archives[0]);
279
+ if (!transformManifest) return archive;
280
+
281
+ const temp = mkdtempSync(join(tmpdir(), "projen-npm-project-"));
282
+ try {
283
+ exec.spawnSync("tar", ["-xzf", archive, "-C", temp], {
284
+ cwd: process.cwd(),
285
+ stdout: "ignore",
286
+ stderr: "inherit",
287
+ stdin: "ignore",
288
+ check: true,
289
+ });
290
+ const manifestPath = join(temp, "package", "package.json");
291
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as NpmArchiveManifest;
292
+ writeFileSync(manifestPath, `${JSON.stringify(transformManifest(manifest), null, 2)}\n`);
293
+ rmSync(archive, { force: true });
294
+ exec.spawnSync("tar", ["-czf", archive, "-C", temp, "package"], {
295
+ cwd: process.cwd(),
296
+ stdout: "ignore",
297
+ stderr: "inherit",
298
+ stdin: "ignore",
299
+ check: true,
300
+ });
301
+ return archive;
302
+ } finally {
303
+ rmSync(temp, { recursive: true, force: true });
304
+ }
263
305
  }
264
306
 
265
307
  export async function publishNpmArchives(options: {
@@ -291,23 +333,13 @@ export async function publishNpmArchives(options: {
291
333
  continue;
292
334
  }
293
335
  }
294
- exec.spawnSync(
295
- "npm",
296
- [
297
- "publish",
298
- archive,
299
- ...(local.access ? ["--access", local.access] : []),
300
- ...(options.registry ? ["--registry", options.registry] : []),
301
- ...(options.dryRun ? ["--dry-run"] : []),
302
- ],
303
- {
304
- cwd: process.cwd(),
305
- stdout: "inherit",
306
- stderr: "inherit",
307
- stdin: "ignore",
308
- check: true,
309
- },
310
- );
336
+ runTaskCommand(process.cwd(), "npm", [
337
+ "publish",
338
+ archive,
339
+ ...(local.access ? ["--access", local.access] : []),
340
+ ...(options.registry ? ["--registry", options.registry] : []),
341
+ ...(options.dryRun ? ["--dry-run"] : []),
342
+ ]);
311
343
  }
312
344
  }
313
345
 
@@ -1,26 +1,24 @@
1
1
  #!/usr/bin/env -S bun
2
2
  import {
3
3
  chmodSync,
4
+ cpSync,
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
7
8
  readFileSync,
8
9
  readdirSync,
9
10
  rmSync,
10
- statSync,
11
11
  writeFileSync,
12
12
  } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
- import { basename, join, resolve } from "node:path";
15
- import * as exec from "@dbx-tools/core/exec";
14
+ import { basename, dirname, join, resolve } from "node:path";
16
15
  import { Command } from "commander";
17
16
  import { parse, stringify } from "smol-toml";
18
- import { pythonProjectInfo, stampPythonProject } from "./uniffi-python.js";
19
- import type { ReleasePlan } from "../src/release-plan.ts";
17
+ import { preparePythonProjectForPublication, pythonProjectInfo } from "./uniffi-python.js";
18
+ import { runTaskCommand } from "../src/_task-command.ts";
20
19
 
21
20
  interface PythonProjectFile {
22
21
  readonly directory: string;
23
- readonly mode: number;
24
22
  readonly name: string;
25
23
  readonly version: string;
26
24
  readonly path: string;
@@ -29,12 +27,6 @@ interface PythonProjectFile {
29
27
  readonly uniffi: boolean;
30
28
  }
31
29
 
32
- export interface StampPythonProjectsOptions {
33
- readonly excludeUniFFI?: boolean;
34
- readonly rewriteDependencies?: boolean;
35
- readonly versions?: ReadonlyMap<string, string>;
36
- }
37
-
38
30
  /** Python wheel and source archives accepted by Twine and package indexes. */
39
31
  export function pythonDistributionPaths(directory: string): string[] {
40
32
  return readdirSync(directory)
@@ -43,16 +35,7 @@ export function pythonDistributionPaths(directory: string): string[] {
43
35
  .map((file) => join(directory, file));
44
36
  }
45
37
 
46
- export interface RestorePythonProjects {
47
- (): void;
48
- readonly paths: readonly string[];
49
- }
50
-
51
- export function stampPythonProjects(
52
- root: string,
53
- version: string,
54
- options: StampPythonProjectsOptions = {},
55
- ): RestorePythonProjects {
38
+ function pythonProjects(root: string): PythonProjectFile[] {
56
39
  const packageFiles = readdirSync(root, { withFileTypes: true })
57
40
  .filter((entry) => entry.isDirectory())
58
41
  .map((entry) => resolve(root, entry.name, "pyproject.toml"))
@@ -63,7 +46,6 @@ export function stampPythonProjects(
63
46
  const info = pythonProjectInfo(source, { parse, stringify });
64
47
  return {
65
48
  directory: basename(resolve(path, "..")),
66
- mode: statSync(path).mode,
67
49
  name: info.name,
68
50
  version: info.version,
69
51
  path,
@@ -72,47 +54,7 @@ export function stampPythonProjects(
72
54
  uniffi: info.uniffi,
73
55
  };
74
56
  });
75
- const projects = allProjects.filter(
76
- (project) =>
77
- !project.private &&
78
- (!options.excludeUniFFI || !project.uniffi) &&
79
- (!options.versions || options.versions.has(project.directory)),
80
- );
81
- if (projects.length === 0) throw new Error(`No Python packages found under ${root}`);
82
-
83
- try {
84
- for (const project of projects) {
85
- const stamped = stampPythonProject(project.source, {
86
- packages: allProjects,
87
- rewriteDependencies: options.rewriteDependencies,
88
- usePackageVersions: options.versions !== undefined,
89
- toml: { parse, stringify },
90
- version: options.versions?.get(project.directory) ?? version,
91
- });
92
- chmodSync(project.path, project.mode | 0o200);
93
- writeFileSync(project.path, stamped);
94
- chmodSync(project.path, project.mode);
95
- }
96
- } catch (error) {
97
- for (const project of projects) {
98
- chmodSync(project.path, project.mode | 0o200);
99
- writeFileSync(project.path, project.source);
100
- chmodSync(project.path, project.mode);
101
- }
102
- throw error;
103
- }
104
-
105
- const restore = () => {
106
- for (const project of projects) {
107
- chmodSync(project.path, project.mode | 0o200);
108
- writeFileSync(project.path, project.source);
109
- chmodSync(project.path, project.mode);
110
- }
111
- };
112
- Object.defineProperty(restore, "paths", {
113
- value: projects.map((project) => project.path),
114
- });
115
- return restore as RestorePythonProjects;
57
+ return allProjects;
116
58
  }
117
59
 
118
60
  export function publishPythonProjects(options: {
@@ -121,12 +63,12 @@ export function publishPythonProjects(options: {
121
63
  readonly publishUrl: string;
122
64
  readonly root: string;
123
65
  readonly version: string;
124
- readonly plan?: ReleasePlan;
125
66
  }): void {
126
67
  const output = mkdtempSync(join(tmpdir(), "projen-python-publish-"));
127
68
  try {
128
69
  buildPythonProjects({ ...options, output });
129
- exec.spawnSync(
70
+ runTaskCommand(
71
+ process.cwd(),
130
72
  "uvx",
131
73
  [
132
74
  "--from",
@@ -139,14 +81,7 @@ export function publishPythonProjects(options: {
139
81
  ...(options.dryRun ? ["--dry-run"] : []),
140
82
  output,
141
83
  ],
142
- {
143
- cwd: process.cwd(),
144
- env: { ...process.env, UV_DEFAULT_INDEX: options.indexUrl },
145
- stdout: "inherit",
146
- stderr: "inherit",
147
- stdin: "ignore",
148
- check: true,
149
- },
84
+ { env: { ...process.env, UV_DEFAULT_INDEX: options.indexUrl } },
150
85
  );
151
86
  } finally {
152
87
  rmSync(output, { recursive: true, force: true });
@@ -160,113 +95,76 @@ export function buildPythonProjects(options: {
160
95
  readonly output: string;
161
96
  readonly root: string;
162
97
  readonly version: string;
163
- readonly plan?: ReleasePlan;
164
98
  }): void {
165
99
  const root = resolve(options.root);
166
100
  const output = resolve(options.output);
167
101
  rmSync(output, { recursive: true, force: true });
168
- const standardProjects = options.excludeUniFFI
169
- ? readdirSync(root, { withFileTypes: true })
170
- .filter((entry) => entry.isDirectory())
171
- .map((entry) => resolve(root, entry.name, "pyproject.toml"))
172
- .filter(existsSync)
173
- .map((path) => pythonProjectInfo(readFileSync(path, "utf8"), { parse, stringify }))
174
- .filter((project) => !project.private && !project.uniffi)
175
- : undefined;
176
- if (standardProjects?.length === 0 && options.allowEmpty) {
102
+ const allProjects = pythonProjects(root);
103
+ const projects = allProjects.filter(
104
+ (project) => !project.private && (!options.excludeUniFFI || !project.uniffi),
105
+ );
106
+ if (projects.length === 0 && options.allowEmpty) {
177
107
  mkdirSync(output, { recursive: true });
178
108
  return;
179
109
  }
180
- const planned = new Map(
181
- options.plan?.pythonPackages.map((pkg) => [pkg.path.replace(/^.*\//, ""), pkg.version]) ?? [],
182
- );
183
- const stamp = stampPythonProjects(root, options.version, {
184
- excludeUniFFI: options.excludeUniFFI,
185
- ...(options.plan ? { versions: planned } : {}),
186
- });
110
+ if (projects.length === 0) throw new Error(`No Python packages found under ${root}`);
111
+
112
+ const temporaryRoot = mkdtempSync(join(tmpdir(), "projen-python-release-"));
187
113
  try {
188
- const packages = options.plan?.pythonPackages ?? [];
189
- if (packages.length > 0) {
190
- for (const pkg of packages) {
191
- exec.spawnSync("uv", ["build", "--package", pkg.identity, "--out-dir", output], {
192
- cwd: process.cwd(),
193
- stdout: "inherit",
194
- stderr: "inherit",
195
- stdin: "ignore",
196
- check: true,
197
- });
198
- }
199
- } else if (options.excludeUniFFI) {
200
- for (const project of standardProjects ?? []) {
201
- exec.spawnSync("uv", ["build", "--package", project.name, "--out-dir", output], {
202
- cwd: process.cwd(),
203
- stdout: "inherit",
204
- stderr: "inherit",
205
- stdin: "ignore",
206
- check: true,
207
- });
208
- }
209
- } else {
210
- exec.spawnSync("uv", ["build", "--all-packages", "--out-dir", output], {
211
- cwd: process.cwd(),
212
- stdout: "inherit",
213
- stderr: "inherit",
214
- stdin: "ignore",
215
- check: true,
216
- });
114
+ mkdirSync(output, { recursive: true });
115
+ for (const project of projects) {
116
+ const packageRoot = join(temporaryRoot, project.directory);
117
+ cpSync(dirname(project.path), packageRoot, { recursive: true });
118
+ const manifestPath = join(packageRoot, "pyproject.toml");
119
+ chmodSync(manifestPath, 0o644);
120
+ writeFileSync(
121
+ manifestPath,
122
+ preparePythonProjectForPublication(project.source, {
123
+ packages: allProjects,
124
+ toml: { parse, stringify },
125
+ version: options.version,
126
+ }),
127
+ );
128
+ runTaskCommand(root, "uv", ["build", "--out-dir", output, packageRoot]);
217
129
  }
218
130
  const distributions = pythonDistributionPaths(output);
219
131
  if (distributions.length === 0) {
220
132
  throw new Error(`No Python distributions found in ${output}`);
221
133
  }
222
- exec.spawnSync("uvx", ["twine", "check", ...distributions], {
223
- cwd: process.cwd(),
224
- stdout: "inherit",
225
- stderr: "inherit",
226
- stdin: "ignore",
227
- check: true,
228
- });
134
+ runTaskCommand(process.cwd(), "uvx", ["twine", "check", ...distributions]);
229
135
  } finally {
230
- stamp();
136
+ rmSync(temporaryRoot, { recursive: true, force: true });
231
137
  }
232
138
  }
233
139
 
234
140
  if (import.meta.main) {
235
141
  const program = new Command();
236
142
  program
237
- .argument("[version]", "Python package version")
143
+ .argument("<version>", "Python package version")
238
144
  .requiredOption("--index-url <url>", "devpi Simple API URL")
239
145
  .requiredOption("--publish-url <url>", "devpi writable index URL")
240
146
  .option("--root <path>", "Python workspace package root", "packages/py")
241
- .option("--plan <path>", "affected release plan")
242
147
  .option("--output <path>", "Build distributions into a directory without publishing")
243
148
  .option("--dry-run", "build and inspect distributions without uploading")
244
149
  .action(
245
150
  (
246
- version: string | undefined,
151
+ version: string,
247
152
  options: {
248
153
  dryRun?: boolean;
249
154
  indexUrl: string;
250
155
  publishUrl: string;
251
156
  root: string;
252
- plan?: string;
253
157
  output?: string;
254
158
  },
255
159
  ) => {
256
- const plan = options.plan
257
- ? (JSON.parse(readFileSync(options.plan, "utf8")) as ReleasePlan)
258
- : undefined;
259
- const fallbackVersion = version ?? plan?.pythonPackages[0]?.version;
260
- if (!fallbackVersion) throw new Error("Python publication requires a version or plan");
261
160
  if (options.output) {
262
161
  buildPythonProjects({
263
162
  output: options.output,
264
163
  root: options.root,
265
- version: fallbackVersion,
266
- plan,
164
+ version,
267
165
  });
268
166
  } else {
269
- publishPythonProjects({ ...options, version: fallbackVersion, plan });
167
+ publishPythonProjects({ ...options, version });
270
168
  }
271
169
  },
272
170
  );