@dbx-tools/projen 0.9.6 → 0.9.8
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/README.md +5 -1
- package/index.ts +2 -1
- package/package.json +4 -4
- package/src/_rust-project.ts +46 -0
- package/src/_rust-release-workflow.ts +157 -52
- package/src/barrels.ts +3 -4
- package/src/bun-app.ts +96 -85
- package/src/bun-workflow.ts +60 -52
- package/src/openapi.ts +15 -8
- package/src/project-py.ts +22 -17
- package/src/project-rs.ts +48 -11
- package/src/release-catalog.ts +4 -1
- package/src/release.ts +206 -170
- package/src/watch.ts +4 -1
- package/src/workspace-lock.ts +22 -0
- package/tasks/publish-uniffi-local.ts +80 -7
- package/tasks/release-pr.ts +215 -204
- package/tasks/sync.ts +6 -5
package/src/bun-app.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* `src/`), they are outside the package's `tsconfig` include, so their `Bun.*`
|
|
24
24
|
* usage never trips the `app` package's DOM-only `compile`.
|
|
25
25
|
*/
|
|
26
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
26
27
|
import { type Project, TextFile } from "projen";
|
|
27
28
|
|
|
28
29
|
/** Unmanaged dev-server override module, merged over the generated options if present. */
|
|
@@ -90,34 +91,38 @@ export class BunfigFile extends TextFile {
|
|
|
90
91
|
/** `dev.ts`: a `Bun.serve()` fullstack dev server with React HMR. */
|
|
91
92
|
export class BunDevServerFile extends TextFile {
|
|
92
93
|
constructor(project: Project) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
import { existsSync } from "node:fs";
|
|
98
|
-
import index from "./index.html";
|
|
94
|
+
// prettier-ignore
|
|
95
|
+
const source = (
|
|
96
|
+
// ============================================================================
|
|
97
|
+
/*ts*/`
|
|
98
|
+
import { existsSync } from "node:fs";
|
|
99
|
+
import index from "./index.html";
|
|
99
100
|
|
|
100
|
-
// Unmanaged override (${BUN_DEV_OVERRIDE}): its default export is merged over the
|
|
101
|
-
// generated serve options - later wins - so a package tweaks the dev server
|
|
102
|
-
// without editing this generated file.
|
|
103
|
-
let options: Bun.ServeFunctionOptions<unknown, {}> = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
101
|
+
// Unmanaged override (${BUN_DEV_OVERRIDE}): its default export is merged over the
|
|
102
|
+
// generated serve options - later wins - so a package tweaks the dev server
|
|
103
|
+
// without editing this generated file.
|
|
104
|
+
let options: Bun.ServeFunctionOptions<unknown, {}> = {
|
|
105
|
+
port: Number(process.env.PORT ?? 5173),
|
|
106
|
+
development: { hmr: true, console: true },
|
|
107
|
+
routes: { "/*": index },
|
|
108
|
+
};
|
|
108
109
|
|
|
109
|
-
const overrideUrl = new URL("./${BUN_DEV_OVERRIDE}", import.meta.url);
|
|
110
|
-
if (existsSync(overrideUrl)) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
110
|
+
const overrideUrl = new URL("./${BUN_DEV_OVERRIDE}", import.meta.url);
|
|
111
|
+
if (existsSync(overrideUrl)) {
|
|
112
|
+
const mod = await import(overrideUrl.href);
|
|
113
|
+
const override = typeof mod.default === "function" ? await mod.default() : mod.default;
|
|
114
|
+
options = { ...options, ...override };
|
|
115
|
+
}
|
|
115
116
|
|
|
116
|
-
const server = Bun.serve(options);
|
|
117
|
-
console.log("dev server running at " + server.url.href);
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
const server = Bun.serve(options);
|
|
118
|
+
console.log("dev server running at " + server.url.href);
|
|
119
|
+
`
|
|
120
|
+
// ============================================================================
|
|
121
|
+
);
|
|
122
|
+
super(project, "dev.ts", {
|
|
123
|
+
marker: true,
|
|
124
|
+
readonly: true,
|
|
125
|
+
lines: stringUtils.dedentLines(source, { trimEnd: false }),
|
|
121
126
|
});
|
|
122
127
|
}
|
|
123
128
|
}
|
|
@@ -125,73 +130,79 @@ console.log("dev server running at " + server.url.href);
|
|
|
125
130
|
/** `build.ts`: a `Bun.build()` production bundle (Tailwind via the plugin API). */
|
|
126
131
|
export class BunBuildFile extends TextFile {
|
|
127
132
|
constructor(project: Project) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
import {
|
|
135
|
-
import
|
|
133
|
+
// Raw template preserves backslashes in generated regular expression literals.
|
|
134
|
+
// prettier-ignore
|
|
135
|
+
const source = (
|
|
136
|
+
String.raw
|
|
137
|
+
// ============================================================================
|
|
138
|
+
/*ts*/`
|
|
139
|
+
import { existsSync } from "node:fs";
|
|
140
|
+
import { cp, rm } from "node:fs/promises";
|
|
141
|
+
import { basename } from "node:path";
|
|
142
|
+
import tailwind from "bun-plugin-tailwind";
|
|
136
143
|
|
|
137
|
-
// Unmanaged override (${BUN_BUILD_OVERRIDE}): its default export is merged over
|
|
138
|
-
// the generated build options - later wins.
|
|
139
|
-
let options: Bun.BuildConfig = {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
};
|
|
144
|
+
// Unmanaged override (${BUN_BUILD_OVERRIDE}): its default export is merged over
|
|
145
|
+
// the generated build options - later wins.
|
|
146
|
+
let options: Bun.BuildConfig = {
|
|
147
|
+
entrypoints: ["./index.html"],
|
|
148
|
+
outdir: "./dist",
|
|
149
|
+
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
150
|
+
minify: true,
|
|
151
|
+
splitting: true,
|
|
152
|
+
publicPath: "/",
|
|
153
|
+
external: ["/fonts/*"],
|
|
154
|
+
sourcemap: "none",
|
|
155
|
+
plugins: [tailwind],
|
|
156
|
+
};
|
|
150
157
|
|
|
151
|
-
const overrideUrl = new URL("./${BUN_BUILD_OVERRIDE}", import.meta.url);
|
|
152
|
-
if (existsSync(overrideUrl)) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
158
|
+
const overrideUrl = new URL("./${BUN_BUILD_OVERRIDE}", import.meta.url);
|
|
159
|
+
if (existsSync(overrideUrl)) {
|
|
160
|
+
const mod = await import(overrideUrl.href);
|
|
161
|
+
const override = typeof mod.default === "function" ? await mod.default() : mod.default;
|
|
162
|
+
options = { ...options, ...override, plugins: [...(options.plugins ?? []), ...(override.plugins ?? [])] };
|
|
163
|
+
}
|
|
157
164
|
|
|
158
|
-
const outdir = options.outdir ?? "./dist";
|
|
159
|
-
await rm(outdir, { recursive: true, force: true });
|
|
165
|
+
const outdir = options.outdir ?? "./dist";
|
|
166
|
+
await rm(outdir, { recursive: true, force: true });
|
|
160
167
|
|
|
161
|
-
const result = await Bun.build(options);
|
|
162
|
-
if (!result.success) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
168
|
+
const result = await Bun.build(options);
|
|
169
|
+
if (!result.success) {
|
|
170
|
+
for (const message of result.logs) console.error(message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
166
173
|
|
|
167
|
-
// Fix a Bun bug: with \`splitting: true\` and an HTML entrypoint, the emitted
|
|
168
|
-
// HTML's <script src> is wired to an arbitrary chunk instead of the JS
|
|
169
|
-
// entry-point, so the app never boots (a blank page). Rewrite the HTML script
|
|
170
|
-
// (and stylesheet) to the real entry-point outputs. No-op when they already
|
|
171
|
-
// match (e.g. splitting off), so it is always safe to run.
|
|
172
|
-
const htmlOut = result.outputs.find((o) => o.path.endsWith(".html"));
|
|
173
|
-
const entryJs = result.outputs.find((o) => o.kind === "entry-point" && o.path.endsWith(".js"));
|
|
174
|
-
const entryCss = result.outputs.find((o) => o.kind === "entry-point" && o.path.endsWith(".css"));
|
|
175
|
-
if (htmlOut && entryJs) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
174
|
+
// Fix a Bun bug: with \`splitting: true\` and an HTML entrypoint, the emitted
|
|
175
|
+
// HTML's <script src> is wired to an arbitrary chunk instead of the JS
|
|
176
|
+
// entry-point, so the app never boots (a blank page). Rewrite the HTML script
|
|
177
|
+
// (and stylesheet) to the real entry-point outputs. No-op when they already
|
|
178
|
+
// match (e.g. splitting off), so it is always safe to run.
|
|
179
|
+
const htmlOut = result.outputs.find((o) => o.path.endsWith(".html"));
|
|
180
|
+
const entryJs = result.outputs.find((o) => o.kind === "entry-point" && o.path.endsWith(".js"));
|
|
181
|
+
const entryCss = result.outputs.find((o) => o.kind === "entry-point" && o.path.endsWith(".css"));
|
|
182
|
+
if (htmlOut && entryJs) {
|
|
183
|
+
let html = await Bun.file(htmlOut.path).text();
|
|
184
|
+
const jsName = basename(entryJs.path);
|
|
185
|
+
html = html.replace(/(<script[^>]*\bsrc=")([^"]*\/)?[^"/]+\.js(")/i, "$1$2" + jsName + "$3");
|
|
186
|
+
if (entryCss) {
|
|
187
|
+
const cssName = basename(entryCss.path);
|
|
188
|
+
html = html.replace(/(<link[^>]*\bhref=")([^"]*\/)?[^"/]+\.css(")/i, "$1$2" + cssName + "$3");
|
|
189
|
+
}
|
|
190
|
+
await Bun.write(htmlOut.path, html);
|
|
191
|
+
}
|
|
185
192
|
|
|
186
|
-
const publicDir = "./public";
|
|
187
|
-
if (existsSync(publicDir)) {
|
|
188
|
-
|
|
189
|
-
}
|
|
193
|
+
const publicDir = "./public";
|
|
194
|
+
if (existsSync(publicDir)) {
|
|
195
|
+
await cp(publicDir, outdir, { recursive: true });
|
|
196
|
+
}
|
|
190
197
|
|
|
191
|
-
console.log("built " + result.outputs.length + " files to " + options.outdir);
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
console.log("built " + result.outputs.length + " files to " + options.outdir);
|
|
199
|
+
`
|
|
200
|
+
// ============================================================================
|
|
201
|
+
);
|
|
202
|
+
super(project, "build.ts", {
|
|
203
|
+
marker: true,
|
|
204
|
+
readonly: true,
|
|
205
|
+
lines: stringUtils.dedentLines(source, { trimEnd: false }),
|
|
195
206
|
});
|
|
196
207
|
}
|
|
197
208
|
}
|
package/src/bun-workflow.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Shared Bun setup and package-cache steps for generated workflows. */
|
|
2
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
2
3
|
import { TextFile, javascript } from "projen";
|
|
3
4
|
|
|
4
5
|
export const BUN_VERSION = "1.3.14";
|
|
@@ -16,61 +17,68 @@ const DEFAULT_CACHE_IGNORE_PATHS = [
|
|
|
16
17
|
|
|
17
18
|
function cacheKeyScript(extraIgnorePaths: readonly string[]): string {
|
|
18
19
|
const ignorePaths = [...new Set(extraIgnorePaths)].sort();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// prettier-ignore
|
|
21
|
+
const source = (
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/*js*/`
|
|
24
|
+
#!/usr/bin/env node
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
27
|
+
import { join, sep } from "node:path";
|
|
23
28
|
|
|
24
|
-
const root = process.cwd();
|
|
25
|
-
const ignoredNames = new Set(${JSON.stringify(DEFAULT_CACHE_IGNORE_PATHS)});
|
|
26
|
-
const ignoredPaths = new Set(${JSON.stringify(ignorePaths)});
|
|
27
|
-
const manifests = [];
|
|
28
|
-
const walk = (directory) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
walk(root);
|
|
29
|
+
const root = process.cwd();
|
|
30
|
+
const ignoredNames = new Set(${JSON.stringify(DEFAULT_CACHE_IGNORE_PATHS)});
|
|
31
|
+
const ignoredPaths = new Set(${JSON.stringify(ignorePaths)});
|
|
32
|
+
const manifests = [];
|
|
33
|
+
const walk = (directory) => {
|
|
34
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
35
|
+
const path = join(directory, entry.name);
|
|
36
|
+
const relativePath = path.slice(root.length + 1).split(sep).join("/");
|
|
37
|
+
if (ignoredNames.has(entry.name) || ignoredPaths.has(relativePath)) continue;
|
|
38
|
+
if (entry.isDirectory()) walk(path);
|
|
39
|
+
else if (entry.name === "package.json") manifests.push(path);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
walk(root);
|
|
38
43
|
|
|
39
|
-
const dependencyFields = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
];
|
|
50
|
-
const canonical = (value) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
const dependencyFields = [
|
|
45
|
+
"catalog",
|
|
46
|
+
"dependencies",
|
|
47
|
+
"devDependencies",
|
|
48
|
+
"optionalDependencies",
|
|
49
|
+
"overrides",
|
|
50
|
+
"peerDependencies",
|
|
51
|
+
"peerDependenciesMeta",
|
|
52
|
+
"resolutions",
|
|
53
|
+
"trustedDependencies",
|
|
54
|
+
];
|
|
55
|
+
const canonical = (value) => {
|
|
56
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
57
|
+
if (!value || typeof value !== "object") return value;
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(value)
|
|
60
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
61
|
+
.map(([key, child]) => [key, canonical(child)]),
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
const dependencies = manifests
|
|
65
|
+
.sort()
|
|
66
|
+
.map((path) => {
|
|
67
|
+
const manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
return [
|
|
69
|
+
path.slice(root.length + 1),
|
|
70
|
+
Object.fromEntries(
|
|
71
|
+
dependencyFields
|
|
72
|
+
.filter((field) => manifest[field] !== undefined)
|
|
73
|
+
.map((field) => [field, canonical(manifest[field])]),
|
|
74
|
+
),
|
|
75
|
+
];
|
|
76
|
+
});
|
|
77
|
+
process.stdout.write(createHash("sha256").update(JSON.stringify(dependencies)).digest("hex"));
|
|
78
|
+
`
|
|
79
|
+
// ============================================================================
|
|
57
80
|
);
|
|
58
|
-
};
|
|
59
|
-
const dependencies = manifests
|
|
60
|
-
.sort()
|
|
61
|
-
.map((path) => {
|
|
62
|
-
const manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
63
|
-
return [
|
|
64
|
-
path.slice(root.length + 1),
|
|
65
|
-
Object.fromEntries(
|
|
66
|
-
dependencyFields
|
|
67
|
-
.filter((field) => manifest[field] !== undefined)
|
|
68
|
-
.map((field) => [field, canonical(manifest[field])]),
|
|
69
|
-
),
|
|
70
|
-
];
|
|
71
|
-
});
|
|
72
|
-
process.stdout.write(createHash("sha256").update(JSON.stringify(dependencies)).digest("hex"));
|
|
73
|
-
`;
|
|
81
|
+
return stringUtils.dedent(source, { trimEnd: false });
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
const configured = new WeakMap<javascript.NodeProject, string>();
|
package/src/openapi.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { join } from "node:path";
|
|
|
37
37
|
import { promisify } from "node:util";
|
|
38
38
|
import * as bin from "@dbx-tools/core/bin";
|
|
39
39
|
import { find } from "@dbx-tools/path";
|
|
40
|
-
import { log } from "@dbx-tools/shared-core";
|
|
40
|
+
import { log, stringUtils } from "@dbx-tools/shared-core";
|
|
41
41
|
import type * as ts from "typescript";
|
|
42
42
|
import { lazyRequire } from "./_lazy-require.ts";
|
|
43
43
|
import { makeReadonly, makeWritable, stampGenerated } from "./generated.ts";
|
|
@@ -61,14 +61,21 @@ const SPEAKEASY_OPENAPI_VERSION = "1.24.0";
|
|
|
61
61
|
const SPEAKEASY_OPENAPI_RELEASE_URL = `https://github.com/speakeasy-api/openapi/releases/download/v${SPEAKEASY_OPENAPI_VERSION}`;
|
|
62
62
|
const execFileAsync = promisify(execFile);
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// prettier-ignore
|
|
65
|
+
const CLIENT_TEMPLATE = (
|
|
66
|
+
// ============================================================================
|
|
67
|
+
/*ts*/`
|
|
68
|
+
import createClient, { type ClientOptions } from "openapi-fetch";
|
|
69
|
+
import type { paths } from "./schema";
|
|
66
70
|
|
|
67
|
-
/** Create a typed OpenAPI client (openapi-fetch); safe to use in the browser. */
|
|
68
|
-
export function createApiClient(options?: ClientOptions) {
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
71
|
+
/** Create a typed OpenAPI client (openapi-fetch); safe to use in the browser. */
|
|
72
|
+
export function createApiClient(options?: ClientOptions) {
|
|
73
|
+
return createClient<paths>(options);
|
|
74
|
+
}
|
|
75
|
+
`
|
|
76
|
+
// ============================================================================
|
|
77
|
+
);
|
|
78
|
+
const CLIENT_SRC = stringUtils.dedent(CLIENT_TEMPLATE, { trimEnd: false });
|
|
72
79
|
|
|
73
80
|
/** True if any module file in `<pkg>/src` matches {@link TSOA_IMPORT}. */
|
|
74
81
|
function hasTsoaControllers(pkg: Pick<RecordedPackage, "dir">): boolean {
|
package/src/project-py.ts
CHANGED
|
@@ -766,23 +766,28 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
766
766
|
];
|
|
767
767
|
const helper = ".projen/pypi-trusted-publisher-instructions.mjs";
|
|
768
768
|
new TextFile(project.root, helper, {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
769
|
+
// prettier-ignore
|
|
770
|
+
lines: stringUtils.dedent(
|
|
771
|
+
// ============================================================================
|
|
772
|
+
/*js*/`
|
|
773
|
+
#!/usr/bin/env node
|
|
774
|
+
import { parseArgs } from "node:util";
|
|
775
|
+
|
|
776
|
+
const { values } = parseArgs({
|
|
777
|
+
options: { secretFile: { type: "string" } },
|
|
778
|
+
});
|
|
779
|
+
const beforeAuthentication = ${JSON.stringify(linesBeforeAuthentication)};
|
|
780
|
+
const afterAuthentication = ${JSON.stringify(linesAfterAuthentication)};
|
|
781
|
+
const authentication = values.secretFile
|
|
782
|
+
? [
|
|
783
|
+
\`- If browser authentication is required, read credentials from \${values.secretFile} directly into the browser without printing, logging, or exposing secret values.\`,
|
|
784
|
+
\`- If \${values.secretFile} is absent, invalid, or insufficient for authentication, pause and ask the user to complete authentication.\`,
|
|
785
|
+
]
|
|
786
|
+
: ["- If browser authentication is required, pause and ask the user to complete authentication."];
|
|
787
|
+
process.stdout.write(\`\${[...beforeAuthentication, ...authentication, ...afterAuthentication].join("\\n").trimEnd()}\\n\`);
|
|
788
|
+
`
|
|
789
|
+
// ============================================================================
|
|
790
|
+
).split("\n"),
|
|
786
791
|
});
|
|
787
792
|
project.root.addTask("pypiTrustedPublisherInstructions", {
|
|
788
793
|
description: "Print system-browser instructions for PyPI trusted publishers",
|
package/src/project-rs.ts
CHANGED
|
@@ -289,9 +289,11 @@ export interface RustReleaseBinaryMapping {
|
|
|
289
289
|
readonly command: string;
|
|
290
290
|
readonly description: string;
|
|
291
291
|
readonly binaryName: string;
|
|
292
|
+
readonly hidden: boolean;
|
|
292
293
|
readonly unit: string;
|
|
293
294
|
readonly component: string;
|
|
294
295
|
readonly version: string;
|
|
296
|
+
readonly tagPrefix: string;
|
|
295
297
|
readonly tag: string;
|
|
296
298
|
readonly repository: string;
|
|
297
299
|
readonly assets: readonly RustReleaseBinaryAssetMapping[];
|
|
@@ -379,17 +381,40 @@ function planRustReleaseBinaries(
|
|
|
379
381
|
resolved: ResolvedRustWorkspaceOptions,
|
|
380
382
|
): RustReleaseBinaryMapping[] {
|
|
381
383
|
const commands = new Set<string>();
|
|
382
|
-
const binaries = packages
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
const binaries = packages.flatMap((pkg) => {
|
|
385
|
+
const configuredBinaries = [
|
|
386
|
+
...(pkg.packageOptions.cli
|
|
387
|
+
? [
|
|
388
|
+
{
|
|
389
|
+
name: pkg.packageOptions.binaryName ?? pkg.crateName,
|
|
390
|
+
defaultCommand: pkg.packageOptions.directory,
|
|
391
|
+
description: pkg.packageOptions.description,
|
|
392
|
+
release: pkg.packageOptions.release,
|
|
393
|
+
excludedOs: pkg.packageOptions.releaseExcludeOs,
|
|
394
|
+
cli: pkg.packageOptions.cli,
|
|
395
|
+
},
|
|
396
|
+
]
|
|
397
|
+
: []),
|
|
398
|
+
...(pkg.packageOptions.binaries ?? [])
|
|
399
|
+
.filter((binary) => Boolean(binary.cli))
|
|
400
|
+
.map((binary) => ({
|
|
401
|
+
name: binary.name,
|
|
402
|
+
defaultCommand: binary.name,
|
|
403
|
+
description: binary.description ?? pkg.packageOptions.description,
|
|
404
|
+
release: binary.release,
|
|
405
|
+
excludedOs: binary.releaseExcludeOs,
|
|
406
|
+
cli: binary.cli!,
|
|
407
|
+
})),
|
|
408
|
+
];
|
|
409
|
+
return configuredBinaries.map((binary) => {
|
|
410
|
+
if (!binary.release) {
|
|
386
411
|
throw new Error(`${pkg.crateName} must set release when cli is configured`);
|
|
387
412
|
}
|
|
388
|
-
const configured =
|
|
413
|
+
const configured = binary.cli;
|
|
389
414
|
const command =
|
|
390
415
|
typeof configured === "object" && configured.command
|
|
391
416
|
? configured.command
|
|
392
|
-
:
|
|
417
|
+
: binary.defaultCommand;
|
|
393
418
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(command)) {
|
|
394
419
|
throw new Error(`Invalid Rust CLI command: ${command}`);
|
|
395
420
|
}
|
|
@@ -399,24 +424,31 @@ function planRustReleaseBinaries(
|
|
|
399
424
|
commands.add(command);
|
|
400
425
|
const description =
|
|
401
426
|
(typeof configured === "object" ? configured.description : undefined) ??
|
|
402
|
-
|
|
427
|
+
binary.description ??
|
|
403
428
|
pkg.crateName;
|
|
404
|
-
const
|
|
405
|
-
const
|
|
429
|
+
const hidden = typeof configured === "object" && configured.hidden === true;
|
|
430
|
+
const excludedOs = new Set(binary.excludedOs ?? []);
|
|
431
|
+
const binaryName = binary.name;
|
|
406
432
|
const identity = isDBXToolsJavaScriptProject()(project)
|
|
407
433
|
? project.releaseCatalog.releaseIdentityFor(pkg)
|
|
408
434
|
: {
|
|
409
435
|
component: defaultReleaseUnitId("rust", pkg.crateName),
|
|
410
436
|
version: readWorkspaceVersion(project.outdir),
|
|
411
437
|
};
|
|
438
|
+
const tagPrefix =
|
|
439
|
+
isDBXToolsJavaScriptProject()(project) && project.releaseCatalog.mode === "independent"
|
|
440
|
+
? `${identity.component}-v`
|
|
441
|
+
: "v";
|
|
412
442
|
return {
|
|
413
443
|
command,
|
|
414
444
|
description,
|
|
415
445
|
binaryName,
|
|
446
|
+
hidden,
|
|
416
447
|
unit: "id" in identity ? identity.id : identity.component,
|
|
417
448
|
component: identity.component,
|
|
418
449
|
version: identity.version,
|
|
419
|
-
|
|
450
|
+
tagPrefix,
|
|
451
|
+
tag: `${tagPrefix}${identity.version}`,
|
|
420
452
|
repository: resolved.repository,
|
|
421
453
|
assets: resolved.nativeTargets
|
|
422
454
|
.filter((target) => !excludedOs.has(target.os))
|
|
@@ -427,6 +459,7 @@ function planRustReleaseBinaries(
|
|
|
427
459
|
})),
|
|
428
460
|
};
|
|
429
461
|
});
|
|
462
|
+
});
|
|
430
463
|
if (binaries.length && !resolved.repository) {
|
|
431
464
|
throw new Error("A repository URL is required when Rust CLI commands are configured");
|
|
432
465
|
}
|
|
@@ -894,6 +927,10 @@ export class DBXToolsRustWorkspace extends Component {
|
|
|
894
927
|
identity: pkg.crateName,
|
|
895
928
|
publish: !pkg.packageOptions.private || pkg.packageOptions.release === true,
|
|
896
929
|
sourcePaths: [`${toPosix(relative(project.outdir, pkg.outdir))}/src`],
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
for (const pkg of this.packages) {
|
|
933
|
+
project.releaseCatalog.configureProject(pkg, {
|
|
897
934
|
dependencies: [
|
|
898
935
|
...rustReleaseDependencies(
|
|
899
936
|
project,
|
|
@@ -921,7 +958,7 @@ export class DBXToolsRustWorkspace extends Component {
|
|
|
921
958
|
if (isDBXToolsJavaScriptProject()(project)) {
|
|
922
959
|
for (const binary of this.releaseBinaries) {
|
|
923
960
|
project.releaseCatalog.registerArtifact(binary.unit, {
|
|
924
|
-
id: `${binary.unit}:github-binary`,
|
|
961
|
+
id: `${binary.unit}:github-binary:${binary.binaryName}`,
|
|
925
962
|
kind: "github-binary",
|
|
926
963
|
name: binary.binaryName,
|
|
927
964
|
generated: true,
|
package/src/release-catalog.ts
CHANGED
|
@@ -311,7 +311,10 @@ export class DBXToolsReleaseCatalog extends Component {
|
|
|
311
311
|
/** Replace grouping metadata for an already registered project. */
|
|
312
312
|
configureProject(
|
|
313
313
|
project: Project,
|
|
314
|
-
options: Pick<
|
|
314
|
+
options: Pick<
|
|
315
|
+
ReleaseProjectRegistration,
|
|
316
|
+
"unit" | "component" | "publish" | "sourcePaths" | "dependencies"
|
|
317
|
+
>,
|
|
315
318
|
): void {
|
|
316
319
|
const current = this.projects.get(project);
|
|
317
320
|
if (!current) throw new Error(`Release project is not registered: ${project.outdir}`);
|