@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.
- package/._LICENSE +0 -0
- package/._README.md +0 -0
- package/._index.ts +0 -0
- package/._package.json +0 -0
- package/README.md +72 -88
- package/index.ts +2 -7
- package/package.json +9 -5
- package/src/.__lazy-require.ts +0 -0
- package/src/.__release-platform.ts +0 -0
- package/src/.__rust-project.ts +0 -0
- package/src/.__rust-release-workflow.ts +0 -0
- package/src/.__task-command.ts +0 -0
- package/src/._barrels.ts +0 -0
- package/src/._bun-app.ts +0 -0
- package/src/._bun-workflow.ts +0 -0
- package/src/._clean.ts +0 -0
- package/src/._codegen.ts +0 -0
- package/src/._dbx-tools-config.ts +0 -0
- package/src/._engine-root.ts +0 -0
- package/src/._generated.ts +0 -0
- package/src/._mixin.ts +0 -0
- package/src/._module-exports.ts +0 -0
- package/src/._openapi.ts +0 -0
- package/src/._packages.ts +0 -0
- package/src/._pnpm-workspace.ts +0 -0
- package/src/._project-js.ts +0 -0
- package/src/._project-predicate.ts +0 -0
- package/src/._project-py.ts +0 -0
- package/src/._project-rs.ts +0 -0
- package/src/._project.ts +0 -0
- package/src/._projen-version.ts +0 -0
- package/src/._publish.ts +0 -0
- package/src/._release-catalog.ts +0 -0
- package/src/._release-dispatch.ts +0 -0
- package/src/._release-github.ts +0 -0
- package/src/._release.ts +0 -0
- package/src/._scaffold.ts +0 -0
- package/src/._tags.ts +0 -0
- package/src/._tsconfig.ts +0 -0
- package/src/._uniffi.ts +0 -0
- package/src/._vscode.ts +0 -0
- package/src/._watch.ts +0 -0
- package/src/._workspace-lock.ts +0 -0
- package/src/._workspace-version.ts +0 -0
- package/src/_rust-release-workflow.ts +18 -582
- package/src/_task-command.ts +75 -0
- package/src/project-js.ts +2 -42
- package/src/project-py.ts +60 -162
- package/src/project-rs.ts +4 -100
- package/src/release-catalog.ts +37 -203
- package/src/release.ts +96 -775
- package/src/scaffold.ts +2 -6
- package/src/workspace-version.ts +9 -121
- package/tasks/._barrels.ts +0 -0
- package/tasks/._bump.ts +0 -0
- package/tasks/._clean.ts +0 -0
- package/tasks/._compile-workspace.ts +0 -0
- package/tasks/._local-publish.ts +0 -0
- package/tasks/._openapi.ts +0 -0
- package/tasks/._projenrc.ts +0 -0
- package/tasks/._publish-npm.ts +0 -0
- package/tasks/._publish-python.ts +0 -0
- package/tasks/._publish-uniffi-local.ts +0 -0
- package/tasks/._publish.ts +0 -0
- package/tasks/._python-registry.ts +0 -0
- package/tasks/._release-assets.ts +0 -0
- package/tasks/._release-candidate.ts +0 -0
- package/tasks/._release-manifest.ts +0 -0
- package/tasks/._release-pr.ts +0 -0
- package/tasks/._release-summary.ts +0 -0
- package/tasks/._release-version.ts +0 -0
- package/tasks/._rust.ts +0 -0
- package/tasks/._sync.ts +0 -0
- package/tasks/._uniffi-python.d.ts +0 -0
- package/tasks/._uniffi-python.js +0 -0
- package/tasks/._uniffi-release.mjs +0 -0
- package/tasks/._uniffi.ts +0 -0
- package/tasks/._version-check.ts +0 -0
- package/tasks/bump.ts +17 -34
- package/tasks/local-publish.ts +124 -117
- package/tasks/publish-npm.ts +125 -41
- package/tasks/publish-python.ts +79 -106
- package/tasks/publish-uniffi-local.ts +129 -84
- package/tasks/publish.ts +44 -139
- package/tasks/python-registry.ts +2 -9
- package/tasks/release-assets.ts +56 -65
- package/tasks/release-candidate.ts +416 -0
- package/tasks/release-manifest.ts +273 -0
- package/tasks/release-pr.ts +312 -369
- package/tasks/release-summary.ts +8 -10
- package/tasks/release-version.ts +23 -2
- package/tasks/rust.ts +8 -13
- package/tasks/uniffi-python.d.ts +2 -3
- package/tasks/uniffi-python.js +7 -20
- package/tasks/uniffi-release.mjs +19 -13
- package/tasks/uniffi.ts +15 -24
- package/tasks/version-check.ts +24 -56
- package/src/release-plan.ts +0 -269
- package/src/release-please.ts +0 -242
- package/tasks/release-bootstrap.ts +0 -59
- package/tasks/release-plan.ts +0 -89
- package/tasks/release-please.ts +0 -162
- package/tasks/release-request.ts +0 -166
- package/tasks/release-summary-units.ts +0 -75
- package/tasks/stamp-python.ts +0 -13
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
cpSync,
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { arch, platform, tmpdir } from "node:os";
|
|
14
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
15
|
import { fileURLToPath } from "node:url";
|
|
6
16
|
import { parseArgs } from "node:util";
|
|
7
17
|
import { json, log, object } from "@dbx-tools/shared-core";
|
|
@@ -12,33 +22,21 @@ import type { RustBindingMapping, RustWorkspaceMapping } from "../src/project-rs
|
|
|
12
22
|
|
|
13
23
|
const logger = log.logger("projen:publish-uniffi-local");
|
|
14
24
|
|
|
15
|
-
const parsed = parseArgs({
|
|
16
|
-
options: {
|
|
17
|
-
version: { type: "string" },
|
|
18
|
-
registry: { type: "string" },
|
|
19
|
-
"pypi-publish-url": { type: "string" },
|
|
20
|
-
"cargo-registry": { type: "string" },
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
function run(command: string, args: string[], capture = false): string {
|
|
25
|
-
if (capture) return captureTaskCommand(repoRoot, command, args, { check: true });
|
|
26
|
-
runTaskCommand(repoRoot, command, args);
|
|
27
|
-
return "";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
25
|
function commandAvailable(command: string): boolean {
|
|
31
26
|
return taskCommandSucceeds(repoRoot, command, ["--version"]);
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
function rustHost(): string {
|
|
35
|
-
const host =
|
|
30
|
+
const host = captureTaskCommand(repoRoot, "rustc", ["-vV"], { check: true }).match(
|
|
31
|
+
/^host:\s+(.+)$/m,
|
|
32
|
+
)?.[1];
|
|
36
33
|
if (!host) throw new Error("Unable to detect the local Rust target from rustc -vV");
|
|
37
34
|
return host;
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
function pythonTag(): string {
|
|
41
|
-
const value =
|
|
38
|
+
const value = captureTaskCommand(
|
|
39
|
+
repoRoot,
|
|
42
40
|
"uv",
|
|
43
41
|
[
|
|
44
42
|
"run",
|
|
@@ -47,7 +45,7 @@ function pythonTag(): string {
|
|
|
47
45
|
"-c",
|
|
48
46
|
"import sysconfig; print(sysconfig.get_platform().replace('-', '_').replace('.', '_'))",
|
|
49
47
|
],
|
|
50
|
-
true,
|
|
48
|
+
{ check: true },
|
|
51
49
|
);
|
|
52
50
|
if (!value) throw new Error("Unable to detect the local Python wheel platform tag");
|
|
53
51
|
return value;
|
|
@@ -99,12 +97,18 @@ interface CargoMetadataPackage {
|
|
|
99
97
|
dependencies: Array<{ name: string; source?: string | null }>;
|
|
100
98
|
manifest_path: string;
|
|
101
99
|
name: string;
|
|
100
|
+
version: string;
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
function orderedCargoManifests(config: RustWorkspaceMapping):
|
|
105
|
-
const configured = new Set(config.crates.map((crate) => resolve(
|
|
103
|
+
function orderedCargoManifests(root: string, config: RustWorkspaceMapping): CargoMetadataPackage[] {
|
|
104
|
+
const configured = new Set(config.crates.map((crate) => resolve(root, crate, "Cargo.toml")));
|
|
106
105
|
const metadata = json.parseRecord(
|
|
107
|
-
|
|
106
|
+
captureTaskCommand(
|
|
107
|
+
root,
|
|
108
|
+
"cargo",
|
|
109
|
+
["metadata", "--format-version", "1", "--no-deps", "--locked"],
|
|
110
|
+
{ check: true },
|
|
111
|
+
),
|
|
108
112
|
);
|
|
109
113
|
if (!metadata || !Array.isArray(metadata.packages)) {
|
|
110
114
|
throw new Error("Cargo metadata did not return workspace packages");
|
|
@@ -112,6 +116,7 @@ function orderedCargoManifests(config: RustWorkspaceMapping): string[] {
|
|
|
112
116
|
const packages = metadata.packages.filter(object.isRecord).map((pkg) => {
|
|
113
117
|
if (
|
|
114
118
|
typeof pkg.name !== "string" ||
|
|
119
|
+
typeof pkg.version !== "string" ||
|
|
115
120
|
typeof pkg.manifest_path !== "string" ||
|
|
116
121
|
!Array.isArray(pkg.dependencies)
|
|
117
122
|
) {
|
|
@@ -119,6 +124,7 @@ function orderedCargoManifests(config: RustWorkspaceMapping): string[] {
|
|
|
119
124
|
}
|
|
120
125
|
return {
|
|
121
126
|
name: pkg.name,
|
|
127
|
+
version: pkg.version,
|
|
122
128
|
manifest_path: resolve(pkg.manifest_path),
|
|
123
129
|
dependencies: pkg.dependencies.filter(object.isRecord).map((dependency) => {
|
|
124
130
|
if (typeof dependency.name !== "string") {
|
|
@@ -161,79 +167,103 @@ function orderedCargoManifests(config: RustWorkspaceMapping): string[] {
|
|
|
161
167
|
ordered.push(pkg);
|
|
162
168
|
};
|
|
163
169
|
for (const pkg of publishable.values()) visit(pkg);
|
|
164
|
-
return ordered
|
|
170
|
+
return ordered;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function copyCargoWorkspace(config: RustWorkspaceMapping): string {
|
|
174
|
+
const root = mkdtempSync(join(tmpdir(), "dbx-tools-local-cargo-"));
|
|
175
|
+
for (const path of ["Cargo.toml", "Cargo.lock", "LICENSE", "README.md", "rust-toolchain.toml"]) {
|
|
176
|
+
const source = join(repoRoot, path);
|
|
177
|
+
if (!existsSync(source)) continue;
|
|
178
|
+
cpSync(source, join(root, path));
|
|
179
|
+
}
|
|
180
|
+
const cargoConfig = join(repoRoot, ".cargo");
|
|
181
|
+
if (existsSync(cargoConfig)) cpSync(cargoConfig, join(root, ".cargo"), { recursive: true });
|
|
182
|
+
for (const crate of config.crates) {
|
|
183
|
+
const source = resolve(repoRoot, crate);
|
|
184
|
+
const destination = resolve(root, relative(repoRoot, source));
|
|
185
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
186
|
+
cpSync(source, destination, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
return root;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function projectCargoRegistry(
|
|
192
|
+
document: Record<string, unknown>,
|
|
193
|
+
crateNames: ReadonlySet<string>,
|
|
194
|
+
registry: string,
|
|
195
|
+
): void {
|
|
196
|
+
const sections: unknown[] = [
|
|
197
|
+
document.dependencies,
|
|
198
|
+
document["dev-dependencies"],
|
|
199
|
+
document["build-dependencies"],
|
|
200
|
+
];
|
|
201
|
+
const workspace = document.workspace;
|
|
202
|
+
if (workspace && typeof workspace === "object" && !Array.isArray(workspace)) {
|
|
203
|
+
sections.push((workspace as Record<string, unknown>).dependencies);
|
|
204
|
+
}
|
|
205
|
+
for (const value of sections) {
|
|
206
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
207
|
+
const section = value as Record<string, unknown>;
|
|
208
|
+
for (const crateName of crateNames) {
|
|
209
|
+
const dependency = section[crateName];
|
|
210
|
+
if (!dependency || typeof dependency !== "object" || Array.isArray(dependency)) continue;
|
|
211
|
+
const record = dependency as Record<string, unknown>;
|
|
212
|
+
if (record.workspace === true) continue;
|
|
213
|
+
record.registry = registry;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
165
216
|
}
|
|
166
217
|
|
|
167
218
|
function publishCargo(config: RustWorkspaceMapping, registry: string, version: string): void {
|
|
168
|
-
const
|
|
169
|
-
const originals = new Map(
|
|
170
|
-
manifests.map((manifest) => [manifest, readFileSync(manifest, "utf8")]),
|
|
171
|
-
);
|
|
219
|
+
const root = copyCargoWorkspace(config);
|
|
172
220
|
try {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw new Error(`Cargo manifest has no package name: ${manifest}`);
|
|
221
|
+
const packages = orderedCargoManifests(root, config);
|
|
222
|
+
const crateNames = new Set(packages.map((pkg) => pkg.name));
|
|
223
|
+
for (const pkg of packages) {
|
|
224
|
+
if (pkg.version !== version) {
|
|
225
|
+
throw new Error(`${pkg.name} version ${pkg.version} does not match release ${version}`);
|
|
179
226
|
}
|
|
180
|
-
|
|
181
|
-
|
|
227
|
+
}
|
|
228
|
+
const manifests = [join(root, "Cargo.toml"), ...packages.map((pkg) => pkg.manifest_path)];
|
|
182
229
|
for (const manifest of manifests) {
|
|
183
|
-
const mode = statSync(manifest).mode;
|
|
184
230
|
const document = parse(readFileSync(manifest, "utf8")) as Record<string, unknown>;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
for (const sectionName of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
188
|
-
const section = document[sectionName];
|
|
189
|
-
if (!section || typeof section !== "object" || Array.isArray(section)) continue;
|
|
190
|
-
const dependency = (section as Record<string, unknown>)[crateName];
|
|
191
|
-
if (!dependency || typeof dependency !== "object" || Array.isArray(dependency)) {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
(dependency as Record<string, unknown>).registry = registry;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
chmodSync(manifest, mode | 0o200);
|
|
231
|
+
projectCargoRegistry(document, crateNames, registry);
|
|
232
|
+
chmodSync(manifest, 0o644);
|
|
198
233
|
writeFileSync(manifest, `${stringify(document).trimEnd()}\n`);
|
|
199
|
-
chmodSync(manifest, mode);
|
|
200
234
|
}
|
|
201
|
-
for (const
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
logger.info(`skip published ${crateName} @ ${version}`);
|
|
235
|
+
for (const pkg of packages) {
|
|
236
|
+
if (cargoVersionExists(pkg.name, version, registry)) {
|
|
237
|
+
logger.info(`skip published ${pkg.name} @ ${version}`);
|
|
205
238
|
continue;
|
|
206
239
|
}
|
|
207
|
-
|
|
240
|
+
runTaskCommand(root, "cargo", [
|
|
208
241
|
"publish",
|
|
209
|
-
"--
|
|
210
|
-
|
|
242
|
+
"--manifest-path",
|
|
243
|
+
pkg.manifest_path,
|
|
211
244
|
"--registry",
|
|
212
245
|
registry,
|
|
213
|
-
"--
|
|
214
|
-
"--no-verify",
|
|
246
|
+
"--locked",
|
|
215
247
|
]);
|
|
216
248
|
}
|
|
217
249
|
} finally {
|
|
218
|
-
|
|
219
|
-
const mode = statSync(manifest).mode;
|
|
220
|
-
chmodSync(manifest, mode | 0o200);
|
|
221
|
-
writeFileSync(manifest, source);
|
|
222
|
-
chmodSync(manifest, mode);
|
|
223
|
-
}
|
|
250
|
+
rmSync(root, { recursive: true, force: true });
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
253
|
|
|
227
|
-
function buildAndPublish(
|
|
228
|
-
|
|
229
|
-
|
|
254
|
+
function buildAndPublish(
|
|
255
|
+
binding: RustBindingMapping,
|
|
256
|
+
version: string,
|
|
257
|
+
registry: string | undefined,
|
|
258
|
+
pypiPublishUrl: string | undefined,
|
|
259
|
+
): void {
|
|
230
260
|
const includeNode = Boolean(registry && binding.node && binding.nodePackage);
|
|
231
261
|
const includePython = Boolean(pypiPublishUrl && binding.python && binding.pythonPackage);
|
|
232
262
|
if (!includeNode && !includePython) return;
|
|
233
263
|
|
|
234
264
|
const target = nativeTarget();
|
|
235
265
|
const output = resolve(repoRoot, "dist/uniffi");
|
|
236
|
-
|
|
266
|
+
runTaskCommand(repoRoot, "node", [
|
|
237
267
|
resolve(dirname(fileURLToPath(import.meta.url)), "uniffi-release.mjs"),
|
|
238
268
|
"build",
|
|
239
269
|
"--crate",
|
|
@@ -272,7 +302,7 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
272
302
|
const publishNpmScript = resolve(dirname(fileURLToPath(import.meta.url)), "publish-npm.ts");
|
|
273
303
|
for (const directory of [join(output, "npm"), join(output, "npm-facade")]) {
|
|
274
304
|
if (artifacts(directory, ".tgz").length === 0) continue;
|
|
275
|
-
|
|
305
|
+
runTaskCommand(repoRoot, process.execPath, [
|
|
276
306
|
publishNpmScript,
|
|
277
307
|
"--directory",
|
|
278
308
|
directory,
|
|
@@ -286,7 +316,7 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
286
316
|
if (includePython) {
|
|
287
317
|
const wheels = artifacts(join(output, "python"), ".whl");
|
|
288
318
|
if (wheels.length === 0) throw new Error(`No wheel produced for ${binding.crate}`);
|
|
289
|
-
|
|
319
|
+
runTaskCommand(repoRoot, "uvx", [
|
|
290
320
|
"--from",
|
|
291
321
|
"devpi-client",
|
|
292
322
|
"devpi",
|
|
@@ -299,16 +329,31 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
299
329
|
}
|
|
300
330
|
}
|
|
301
331
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
332
|
+
function main(args: string[] = process.argv.slice(2)): void {
|
|
333
|
+
const parsed = parseArgs({
|
|
334
|
+
args,
|
|
335
|
+
options: {
|
|
336
|
+
version: { type: "string" },
|
|
337
|
+
registry: { type: "string" },
|
|
338
|
+
"pypi-publish-url": { type: "string" },
|
|
339
|
+
"cargo-registry": { type: "string" },
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const version = parsed.values.version;
|
|
343
|
+
if (!version) throw new Error("Missing --version");
|
|
344
|
+
const config = rustConfig();
|
|
345
|
+
if (config?.bindings.length) {
|
|
346
|
+
if (!commandAvailable("cargo") || !commandAvailable("rustc")) {
|
|
347
|
+
throw new Error("Cargo and rustc are required because UniFFI Rust projects were detected");
|
|
348
|
+
}
|
|
349
|
+
for (const binding of config.bindings) {
|
|
350
|
+
buildAndPublish(binding, version, parsed.values.registry, parsed.values["pypi-publish-url"]);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const cargoRegistry = parsed.values["cargo-registry"];
|
|
354
|
+
if (cargoRegistry && config?.crates.length) {
|
|
355
|
+
publishCargo(config, cargoRegistry, version);
|
|
308
356
|
}
|
|
309
|
-
for (const binding of config.bindings) buildAndPublish(binding, version);
|
|
310
|
-
}
|
|
311
|
-
const cargoRegistry = parsed.values["cargo-registry"];
|
|
312
|
-
if (cargoRegistry && config?.crates.length) {
|
|
313
|
-
publishCargo(config, cargoRegistry, version);
|
|
314
357
|
}
|
|
358
|
+
|
|
359
|
+
if (import.meta.main) main();
|
package/tasks/publish.ts
CHANGED
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
* version. When Bun's workspace lock already carries the version, its
|
|
19
19
|
* refresh is skipped;
|
|
20
20
|
* - **`publishConfig` substitution** (compiled `lib/` entry points) is done
|
|
21
|
-
*
|
|
21
|
+
* inside the temporary archive, NOT in the checkout: unlike pnpm/npm, `bun
|
|
22
22
|
* publish`/`bun pm pack` do NOT fold `publishConfig`'s `main`/`types`/`bin`/
|
|
23
23
|
* `exports` into the packed manifest (verified: the packed manifest keeps the
|
|
24
24
|
* raw `.ts` source paths and an inert `publishConfig`). Left unsubstituted, a
|
|
25
25
|
* published CLI's `bin` points at `./bin/x.ts`, and because the bin runs via
|
|
26
26
|
* its `#!/usr/bin/env node` shebang, node chokes on the `.ts`
|
|
27
27
|
* (ERR_UNKNOWN_FILE_EXTENSION). We merge `publishConfig` onto the top-level
|
|
28
|
-
* manifest
|
|
28
|
+
* manifest after packing so the tarball advertises the compiled `lib/` tree;
|
|
29
29
|
* - **compiled output** is emitted once, before packing, by one root-level
|
|
30
30
|
* filtered `bun run` that fans out to every publishable member in parallel.
|
|
31
31
|
* Every package is then packed once with lifecycle scripts disabled. The
|
|
@@ -42,26 +42,10 @@
|
|
|
42
42
|
* non-default registry (a local verdaccio); `--exclude <dir>` (repeatable,
|
|
43
43
|
* repo-relative) skips a member owned by another publication flow.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* The disk manifests carry the workspace `VERSION` (projen owns them, read-only);
|
|
49
|
-
* this unlocks each only long enough to fold in the `publishConfig` entry points
|
|
50
|
-
* and publish, then RESTORES every one it touched
|
|
51
|
-
* byte-for-byte (and re-locks the mode) on the way out - see
|
|
52
|
-
* {@link restoreManifests}. Restore returns each manifest to its committed
|
|
53
|
-
* content, which already equals the release version, so the worktree is never
|
|
54
|
-
* left regressed.
|
|
45
|
+
* The checkout remains byte-for-byte unchanged. A stale manifest or lockfile is
|
|
46
|
+
* a release error rather than something publication repairs.
|
|
55
47
|
*/
|
|
56
|
-
import {
|
|
57
|
-
chmodSync,
|
|
58
|
-
existsSync,
|
|
59
|
-
mkdtempSync,
|
|
60
|
-
readFileSync,
|
|
61
|
-
rmSync,
|
|
62
|
-
statSync,
|
|
63
|
-
writeFileSync,
|
|
64
|
-
} from "node:fs";
|
|
48
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
65
49
|
import { tmpdir } from "node:os";
|
|
66
50
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
67
51
|
import { asyncUtils, log } from "@dbx-tools/shared-core";
|
|
@@ -75,25 +59,9 @@ import {
|
|
|
75
59
|
} from "./publish-npm.ts";
|
|
76
60
|
import { runTaskCommand, runTaskCommandAsync } from "../src/_task-command.ts";
|
|
77
61
|
import { toPosix } from "../src/packages.ts";
|
|
78
|
-
import type { ReleasePlan } from "../src/release-plan.ts";
|
|
79
62
|
|
|
80
63
|
const logger = log.logger("projen:publish");
|
|
81
64
|
|
|
82
|
-
/** A manifest's on-disk bytes + mode, captured before this task edits it. */
|
|
83
|
-
interface ManifestBackup {
|
|
84
|
-
readonly content: string;
|
|
85
|
-
readonly mode: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Original state of every manifest this task has unlocked, keyed by path.
|
|
90
|
-
*
|
|
91
|
-
* Bun does not apply `publishConfig` while packing, so publication temporarily
|
|
92
|
-
* projects those entry points onto projen-owned manifests. The projection is
|
|
93
|
-
* only meant for the packed tarball and is restored before the process exits.
|
|
94
|
-
*/
|
|
95
|
-
const manifestBackups = new Map<string, ManifestBackup>();
|
|
96
|
-
|
|
97
65
|
/**
|
|
98
66
|
* Workspace member dirs (absolute), read from the root `pnpm-workspace.yaml` - the
|
|
99
67
|
* file the engine keeps for the Databricks Apps pnpm deploy, which also lists every
|
|
@@ -107,18 +75,13 @@ function workspaceMembers(root: string): string[] {
|
|
|
107
75
|
return (doc?.packages ?? []).map((m) => resolve(root, m));
|
|
108
76
|
}
|
|
109
77
|
|
|
110
|
-
/** Whether selected workspace members carry
|
|
111
|
-
function
|
|
112
|
-
root: string,
|
|
113
|
-
members: readonly string[],
|
|
114
|
-
expected: ReadonlyMap<string, string>,
|
|
115
|
-
): boolean {
|
|
78
|
+
/** Whether selected workspace members carry the reviewed workspace version. */
|
|
79
|
+
function manifestsMatchVersion(members: readonly string[], version: string): boolean {
|
|
116
80
|
return members.every((dir) => {
|
|
117
81
|
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
|
|
118
82
|
version?: string;
|
|
119
83
|
};
|
|
120
|
-
|
|
121
|
-
return pkg.version === expected.get(path);
|
|
84
|
+
return pkg.version === version;
|
|
122
85
|
});
|
|
123
86
|
}
|
|
124
87
|
|
|
@@ -144,41 +107,6 @@ function lockfileMatchesManifestVersions(root: string, members: readonly string[
|
|
|
144
107
|
}
|
|
145
108
|
}
|
|
146
109
|
|
|
147
|
-
/**
|
|
148
|
-
* Make a projen-readonly manifest writable so the publishConfig projection can
|
|
149
|
-
* edit it, recording its original bytes and mode for {@link restoreManifests}
|
|
150
|
-
* the first time it is seen.
|
|
151
|
-
*/
|
|
152
|
-
function unlockManifest(pkgPath: string): void {
|
|
153
|
-
const mode = statSync(pkgPath).mode;
|
|
154
|
-
if (!manifestBackups.has(pkgPath)) {
|
|
155
|
-
manifestBackups.set(pkgPath, { content: readFileSync(pkgPath, "utf8"), mode });
|
|
156
|
-
}
|
|
157
|
-
chmodSync(pkgPath, mode | 0o200);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Put every manifest this task edited back exactly as it was found, mode included.
|
|
162
|
-
*
|
|
163
|
-
* Runs from an `exit` handler so a clean finish and a failure partway through the
|
|
164
|
-
* publish loop are covered alike - a `bun publish` that dies on package 12 of 34
|
|
165
|
-
* must not leave the first 11 projected. Best-effort per file: one unwritable
|
|
166
|
-
* manifest is logged and the rest are still restored, since a partial restore
|
|
167
|
-
* beats none.
|
|
168
|
-
*/
|
|
169
|
-
function restoreManifests(): void {
|
|
170
|
-
for (const [pkgPath, backup] of manifestBackups) {
|
|
171
|
-
try {
|
|
172
|
-
chmodSync(pkgPath, backup.mode | 0o200);
|
|
173
|
-
writeFileSync(pkgPath, backup.content);
|
|
174
|
-
chmodSync(pkgPath, backup.mode);
|
|
175
|
-
} catch (cause) {
|
|
176
|
-
logger.warn(`could not restore ${pkgPath}`, { cause });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
manifestBackups.clear();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
110
|
/** Entry-point fields projen writes as `.ts` source in-repo and rewrites to `lib/` for publish. */
|
|
183
111
|
const PUBLISH_CONFIG_ENTRY_FIELDS = ["main", "types", "bin", "exports"] as const;
|
|
184
112
|
|
|
@@ -193,23 +121,18 @@ function compiledPublishTargets(value: unknown): string[] {
|
|
|
193
121
|
/**
|
|
194
122
|
* Fold a package's `publishConfig` entry-point fields onto the top-level manifest,
|
|
195
123
|
* the way pnpm/npm do at pack time but `bun publish` does NOT (see the module
|
|
196
|
-
* doc).
|
|
197
|
-
* in place (npm ignores it once the top-level fields already point at `lib/`). The
|
|
198
|
-
* manifest must already be unlocked. {@link restoreManifests} puts the `.ts` entry
|
|
199
|
-
* points back at exit, so this only ever affects the packed tarball.
|
|
124
|
+
* doc). The input is the temporary archive manifest, never the checkout.
|
|
200
125
|
*/
|
|
201
|
-
function applyPublishConfig(
|
|
202
|
-
|
|
126
|
+
export function applyPublishConfig(
|
|
127
|
+
source: Readonly<Record<string, unknown>>,
|
|
128
|
+
): Readonly<Record<string, unknown>> {
|
|
129
|
+
const pkg = { ...source };
|
|
203
130
|
const publishConfig = pkg.publishConfig as Record<string, unknown> | undefined;
|
|
204
|
-
if (!publishConfig) return;
|
|
205
|
-
let changed = false;
|
|
131
|
+
if (!publishConfig) return pkg;
|
|
206
132
|
for (const field of PUBLISH_CONFIG_ENTRY_FIELDS) {
|
|
207
|
-
if (field in publishConfig)
|
|
208
|
-
pkg[field] = publishConfig[field];
|
|
209
|
-
changed = true;
|
|
210
|
-
}
|
|
133
|
+
if (field in publishConfig) pkg[field] = publishConfig[field];
|
|
211
134
|
}
|
|
212
|
-
|
|
135
|
+
return pkg;
|
|
213
136
|
}
|
|
214
137
|
|
|
215
138
|
/**
|
|
@@ -227,19 +150,16 @@ const argv = process.argv.slice(2);
|
|
|
227
150
|
const version = argv[0] && !argv[0].startsWith("--") ? argv[0] : undefined;
|
|
228
151
|
const rest = version ? argv.slice(1) : argv;
|
|
229
152
|
const root = process.cwd();
|
|
230
|
-
|
|
231
|
-
const plan =
|
|
232
|
-
planIdx >= 0
|
|
233
|
-
? (JSON.parse(readFileSync(resolve(root, rest[planIdx + 1]), "utf8")) as ReleasePlan)
|
|
234
|
-
: undefined;
|
|
235
|
-
if (!version && !plan) {
|
|
153
|
+
if (!version) {
|
|
236
154
|
logger.error(
|
|
237
|
-
"usage: bun tasks/publish.ts <version> [--
|
|
155
|
+
"usage: bun tasks/publish.ts <version> [--registry <url>] [--output <dir>] [--exclude <dir>] [--dry-run] [--skip-compile]",
|
|
238
156
|
);
|
|
239
157
|
process.exit(1);
|
|
240
158
|
}
|
|
241
159
|
const registryIdx = rest.indexOf("--registry");
|
|
242
160
|
const registry = registryIdx >= 0 ? rest[registryIdx + 1] : undefined;
|
|
161
|
+
const outputIdx = rest.indexOf("--output");
|
|
162
|
+
const output = outputIdx >= 0 ? resolve(root, rest[outputIdx + 1]) : undefined;
|
|
243
163
|
const dryRun = rest.includes("--dry-run");
|
|
244
164
|
const skipCompile = rest.includes("--skip-compile");
|
|
245
165
|
const concurrencyIdx = rest.indexOf("--concurrency");
|
|
@@ -248,46 +168,25 @@ if (!Number.isInteger(parsedConcurrency) || parsedConcurrency < 1) {
|
|
|
248
168
|
throw new Error(`--concurrency must be a positive integer, got ${String(parsedConcurrency)}`);
|
|
249
169
|
}
|
|
250
170
|
const concurrency = parsedConcurrency;
|
|
251
|
-
// Undo transient publishConfig edits at exit.
|
|
252
|
-
const restore = !rest.includes("--no-restore");
|
|
253
171
|
const excluded = new Set(
|
|
254
172
|
rest.reduce<string[]>((acc, arg, i) => (arg === "--exclude" ? [...acc, rest[i + 1]] : acc), []),
|
|
255
173
|
);
|
|
256
174
|
|
|
257
175
|
const path = enrichedPath(root);
|
|
258
|
-
// Registered BEFORE the first manifest edit so no exit path can skip it: a clean
|
|
259
|
-
// finish and a `bun publish` failure mid-loop both land here, so a publish that
|
|
260
|
-
// dies partway does not leave half the workspace stamped.
|
|
261
|
-
if (restore) process.on("exit", restoreManifests);
|
|
262
176
|
const allMembers = workspaceMembers(root)
|
|
263
177
|
.filter((dir) => existsSync(join(dir, "package.json")))
|
|
264
178
|
.filter((dir) => !excluded.has(resolve(root, dir).replace(`${resolve(root)}/`, "")));
|
|
265
|
-
const
|
|
266
|
-
plan
|
|
267
|
-
? plan.nodePackages.map((pkg) => [pkg.path, pkg.version] as const)
|
|
268
|
-
: allMembers.map((dir) => [toPosix(dir.slice(resolve(root).length + 1)), version!]),
|
|
269
|
-
);
|
|
270
|
-
const members = plan
|
|
271
|
-
? allMembers.filter((dir) => expectedVersions.has(toPosix(dir.slice(resolve(root).length + 1))))
|
|
272
|
-
: allMembers;
|
|
179
|
+
const members = allMembers;
|
|
273
180
|
|
|
274
|
-
if (!
|
|
275
|
-
throw new Error(
|
|
181
|
+
if (!manifestsMatchVersion(members, version)) {
|
|
182
|
+
throw new Error(`workspace manifests do not match release ${version}; run projen`);
|
|
276
183
|
}
|
|
277
184
|
logger.info(`validated ${members.length} selected member manifests`);
|
|
278
185
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// live manifests. A normal bump's synth/install already makes it current; after
|
|
282
|
-
// fallback stamping, deleting it before install is what forces re-resolution.
|
|
283
|
-
const lockfile = join(root, "bun.lock");
|
|
284
|
-
if (lockfileMatchesManifestVersions(root, allMembers)) {
|
|
285
|
-
logger.info("workspace lock already resolves live member versions");
|
|
286
|
-
} else {
|
|
287
|
-
if (existsSync(lockfile)) rmSync(lockfile);
|
|
288
|
-
logger.info("refreshing lockfile so workspace deps resolve to the release version");
|
|
289
|
-
runTaskCommand(root, "bun", ["install"], { env: { ...process.env, PATH: path } });
|
|
186
|
+
if (!lockfileMatchesManifestVersions(root, allMembers)) {
|
|
187
|
+
throw new Error("bun.lock does not match workspace versions; run bun install and commit it");
|
|
290
188
|
}
|
|
189
|
+
logger.info("validated workspace lock versions");
|
|
291
190
|
|
|
292
191
|
const publishArgs = [
|
|
293
192
|
"--ignore-scripts",
|
|
@@ -361,24 +260,19 @@ if (compiled.length > 0) {
|
|
|
361
260
|
}
|
|
362
261
|
}
|
|
363
262
|
|
|
364
|
-
// Compile against workspace source exports first. Switching manifests to their
|
|
365
|
-
// publishConfig entries before compilation makes consumers resolve sibling
|
|
366
|
-
// `lib/*.d.ts` files that have not been emitted yet in a clean checkout.
|
|
367
|
-
for (const { dir } of publishable) {
|
|
368
|
-
const manifestPath = join(dir, "package.json");
|
|
369
|
-
unlockManifest(manifestPath);
|
|
370
|
-
applyPublishConfig(manifestPath);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
263
|
logger.info(
|
|
374
|
-
`${dryRun ? "dry-run packing" : "publishing"} ${publishable.length} packages with concurrency ${concurrency}`,
|
|
264
|
+
`${output ? "packing" : dryRun ? "dry-run packing" : "publishing"} ${publishable.length} packages with concurrency ${concurrency}`,
|
|
375
265
|
);
|
|
266
|
+
if (output) {
|
|
267
|
+
rmSync(output, { recursive: true, force: true });
|
|
268
|
+
mkdirSync(output, { recursive: true });
|
|
269
|
+
}
|
|
376
270
|
await asyncUtils.mapConcurrent(
|
|
377
271
|
publishable,
|
|
378
272
|
async ({ dir, name, version: packageVersion, access }) => {
|
|
379
273
|
const packed = mkdtempSync(join(tmpdir(), "projen-npm-release-"));
|
|
380
274
|
try {
|
|
381
|
-
const archive = packNpmPackage(dir, packed, path);
|
|
275
|
+
const archive = packNpmPackage(dir, packed, path, applyPublishConfig);
|
|
382
276
|
const local = readNpmArchiveIdentity(archive);
|
|
383
277
|
if (local.name !== name || local.version !== packageVersion) {
|
|
384
278
|
throw new Error(
|
|
@@ -390,6 +284,15 @@ await asyncUtils.mapConcurrent(
|
|
|
390
284
|
`Packed npm access ${String(local.access)} does not match ${String(access)} for ${name}`,
|
|
391
285
|
);
|
|
392
286
|
}
|
|
287
|
+
if (output) {
|
|
288
|
+
const destination = join(output, archive.split(/[\\/]/).at(-1)!);
|
|
289
|
+
if (existsSync(destination)) {
|
|
290
|
+
throw new Error(`Duplicate npm archive name: ${destination}`);
|
|
291
|
+
}
|
|
292
|
+
copyFileSync(archive, destination);
|
|
293
|
+
logger.info(`packed ${name} @ ${packageVersion}`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
393
296
|
if (!dryRun) {
|
|
394
297
|
const published = await publishedNpmRelease(local.name, local.version, registry);
|
|
395
298
|
if (npmReleaseMatches(local, published)) {
|
|
@@ -410,4 +313,6 @@ await asyncUtils.mapConcurrent(
|
|
|
410
313
|
},
|
|
411
314
|
{ concurrency, errorMode: "settle" },
|
|
412
315
|
);
|
|
413
|
-
logger.success(
|
|
316
|
+
logger.success(
|
|
317
|
+
`${output || dryRun ? "packed" : "published"} ${publishable.length} packages${output ? ` to ${output}` : ""}`,
|
|
318
|
+
);
|
package/tasks/python-registry.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import * as exec from "@dbx-tools/core/exec";
|
|
5
4
|
import { net } from "@dbx-tools/shared-core";
|
|
5
|
+
import { captureTaskCommand } from "../src/_task-command.ts";
|
|
6
6
|
|
|
7
7
|
export interface LocalPythonRegistry {
|
|
8
8
|
readonly indexUrl: string;
|
|
@@ -126,14 +126,7 @@ function splitIndexList(value: string | undefined): string[] {
|
|
|
126
126
|
|
|
127
127
|
/** Read a `pip config get <key>`, treating pip's literal "undefined" as unset. */
|
|
128
128
|
function pipConfig(key: string): string | undefined {
|
|
129
|
-
const
|
|
130
|
-
cwd: process.cwd(),
|
|
131
|
-
stdout: "capture",
|
|
132
|
-
stderr: "ignore",
|
|
133
|
-
stdin: "ignore",
|
|
134
|
-
check: false,
|
|
135
|
-
});
|
|
136
|
-
const out = res.stdout?.trim();
|
|
129
|
+
const out = captureTaskCommand(process.cwd(), "python", ["-m", "pip", "config", "get", key]);
|
|
137
130
|
return out && out !== "undefined" ? out : undefined;
|
|
138
131
|
}
|
|
139
132
|
|