@dbx-tools/projen 0.9.6 → 0.9.7
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 +1 -1
- package/package.json +4 -4
- package/src/_rust-release-workflow.ts +76 -23
- 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/release.ts +206 -170
- package/tasks/sync.ts +3 -3
package/README.md
CHANGED
|
@@ -254,7 +254,11 @@ and select `rust-lld` for the workspace build. Rust release rows restore and
|
|
|
254
254
|
update dependency-only Cargo caches in the release branch's cache scope. The
|
|
255
255
|
cache key excludes workspace version changes, and manual tag recovery restores
|
|
256
256
|
without saving a tag-scoped copy. There is no separate cache workflow or
|
|
257
|
-
sccache layer.
|
|
257
|
+
sccache layer. After publishing the current raw Rust bundles and build manifests,
|
|
258
|
+
the GitHub release job deletes those internal assets from published
|
|
259
|
+
`vMAJOR.MINOR.PATCH` releases strictly lower than the current version while
|
|
260
|
+
leaving versioned consumer binaries in place. Phase timings are written to each
|
|
261
|
+
build log. Python generation executes the already-built
|
|
258
262
|
`target/<triple>/release/<crate>-uniffi-bindgen` directly. Artifact packaging therefore
|
|
259
263
|
does no Rust compilation after the main workspace build.
|
|
260
264
|
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.7";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
package/package.json
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/core": "^0.9.
|
|
34
|
-
"@dbx-tools/path": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.7",
|
|
34
|
+
"@dbx-tools/path": "^0.9.7",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.7",
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
37
|
"concurrently": "^10.0.3",
|
|
38
38
|
"constructs": "^10.6.0",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"main": "index.ts",
|
|
55
55
|
"license": "Apache-2.0",
|
|
56
|
-
"version": "0.9.
|
|
56
|
+
"version": "0.9.7",
|
|
57
57
|
"types": "index.ts",
|
|
58
58
|
"type": "module",
|
|
59
59
|
"exports": {
|
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
6
7
|
import { TextFile, javascript } from "projen";
|
|
7
8
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
8
9
|
import type { RustProject } from "./_rust-project.ts";
|
|
@@ -482,29 +483,34 @@ export function rustBuildJob(plan: RustReleasePlan, independentSetup?: readonly
|
|
|
482
483
|
id: "raw-native",
|
|
483
484
|
shell: "bash",
|
|
484
485
|
env: { GH_TOKEN: "${{ github.token }}" },
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
"
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
486
|
+
// prettier-ignore
|
|
487
|
+
run: stringUtils.dedent(
|
|
488
|
+
// ============================================================================
|
|
489
|
+
/*bash*/`
|
|
490
|
+
KEY="\${{ steps.rust-fingerprint.outputs.key }}"
|
|
491
|
+
test -n "$KEY"
|
|
492
|
+
ASSET="rust-raw-\${{ matrix.node }}-$KEY.tar.gz"
|
|
493
|
+
mkdir -p dist/rust-raw
|
|
494
|
+
MATCH="$(gh api --paginate "repos/\${{ github.repository }}/releases?per_page=100" --jq '.[] | select(.draft == false) | . as $release | .assets[] | select(.name == "'"$ASSET"'") | [$release.tag_name, .url] | @tsv' | head -n 1)"
|
|
495
|
+
if [ -n "$MATCH" ]; then
|
|
496
|
+
TAG="\${MATCH%%$'\\t'*}"
|
|
497
|
+
URL="\${MATCH#*$'\\t'}"
|
|
498
|
+
CHECKSUM_URL="$(gh api "repos/\${{ github.repository }}/releases/tags/$TAG" --jq '.assets[] | select(.name == "'"$ASSET.sha256"'") | .url')"
|
|
499
|
+
rm -rf "target/\${{ matrix.cargo }}/release"
|
|
500
|
+
if test -n "$CHECKSUM_URL" && gh api "$URL" -H "Accept: application/octet-stream" > "dist/rust-raw/$ASSET" && gh api "$CHECKSUM_URL" -H "Accept: application/octet-stream" > "dist/rust-raw/$ASSET.sha256" && (cd dist/rust-raw && if command -v sha256sum >/dev/null 2>&1; then sha256sum --check "$ASSET.sha256"; else shasum -a 256 --check "$ASSET.sha256"; fi) && tar -xzf "dist/rust-raw/$ASSET"; then
|
|
501
|
+
echo "hit=true" >> "$GITHUB_OUTPUT"
|
|
502
|
+
else
|
|
503
|
+
rm -rf "target/\${{ matrix.cargo }}/release" "dist/rust-raw/$ASSET" "dist/rust-raw/$ASSET.sha256"
|
|
504
|
+
echo "::warning::matching raw Rust bundle failed validation; rebuilding"
|
|
505
|
+
echo "hit=false" >> "$GITHUB_OUTPUT"
|
|
506
|
+
fi
|
|
507
|
+
else
|
|
508
|
+
echo "hit=false" >> "$GITHUB_OUTPUT"
|
|
509
|
+
fi
|
|
510
|
+
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
|
|
511
|
+
`
|
|
512
|
+
// ============================================================================
|
|
513
|
+
),
|
|
508
514
|
},
|
|
509
515
|
]
|
|
510
516
|
: []),
|
|
@@ -757,6 +763,53 @@ export function rustGitHubReleaseJob(): Job {
|
|
|
757
763
|
target_commitish: RELEASE_SHA,
|
|
758
764
|
},
|
|
759
765
|
},
|
|
766
|
+
{
|
|
767
|
+
name: "Delete superseded raw Rust release assets",
|
|
768
|
+
uses: "actions/github-script@v8",
|
|
769
|
+
env: { CURRENT_RELEASE_TAG: RELEASE_TAG },
|
|
770
|
+
with: {
|
|
771
|
+
// prettier-ignore
|
|
772
|
+
script: stringUtils.dedent(
|
|
773
|
+
// ============================================================================
|
|
774
|
+
/*js*/`
|
|
775
|
+
const currentTag = process.env.CURRENT_RELEASE_TAG;
|
|
776
|
+
if (!currentTag) throw new Error("CURRENT_RELEASE_TAG is required");
|
|
777
|
+
const parseVersion = (tag) => {
|
|
778
|
+
const match = /^v(\\d+)\\.(\\d+)\\.(\\d+)$/.exec(tag);
|
|
779
|
+
return match ? match.slice(1).map(Number) : undefined;
|
|
780
|
+
};
|
|
781
|
+
const compareVersions = (left, right) => {
|
|
782
|
+
for (let index = 0; index < 3; index += 1) {
|
|
783
|
+
if (left[index] !== right[index]) return left[index] - right[index];
|
|
784
|
+
}
|
|
785
|
+
return 0;
|
|
786
|
+
};
|
|
787
|
+
const currentVersion = parseVersion(currentTag);
|
|
788
|
+
if (!currentVersion) throw new Error(\`Unsupported release tag: \${currentTag}\`);
|
|
789
|
+
const releases = await github.paginate(github.rest.repos.listReleases, {
|
|
790
|
+
...context.repo,
|
|
791
|
+
per_page: 100,
|
|
792
|
+
});
|
|
793
|
+
const assets = releases.flatMap((release) => {
|
|
794
|
+
const version = parseVersion(release.tag_name);
|
|
795
|
+
if (release.draft || !version || compareVersions(version, currentVersion) >= 0) {
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
return release.assets.filter((asset) => /^rust-(?:raw|build)-/.test(asset.name));
|
|
799
|
+
});
|
|
800
|
+
for (const asset of assets) {
|
|
801
|
+
core.info(\`Deleting \${asset.name} (\${asset.id})\`);
|
|
802
|
+
await github.rest.repos.deleteReleaseAsset({
|
|
803
|
+
...context.repo,
|
|
804
|
+
asset_id: asset.id,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
core.info(\`Deleted \${assets.length} superseded raw Rust release assets\`);
|
|
808
|
+
`
|
|
809
|
+
// ============================================================================
|
|
810
|
+
),
|
|
811
|
+
},
|
|
812
|
+
},
|
|
760
813
|
],
|
|
761
814
|
};
|
|
762
815
|
}
|
package/src/barrels.ts
CHANGED
|
@@ -523,10 +523,9 @@ const WRITE_ATTEMPTS = 3;
|
|
|
523
523
|
* and the projenrc watcher's post-synth `generateBarrels()` sweep), so any gap
|
|
524
524
|
* between `makeWritable` and the write is a window in which another process's
|
|
525
525
|
* `makeReadonly` lands and this write fails with
|
|
526
|
-
* `EACCES: permission denied, open '<pkg>/index.ts'`.
|
|
527
|
-
* the
|
|
528
|
-
*
|
|
529
|
-
* and a retry absorbs what is left.
|
|
526
|
+
* `EACCES: permission denied, open '<pkg>/index.ts'`. Keeping the unlock adjacent
|
|
527
|
+
* to the write avoids a gap during export parsing, and a retry absorbs what is
|
|
528
|
+
* left.
|
|
530
529
|
*
|
|
531
530
|
* Do NOT "simplify" this back to a single unlock-then-write: the failure is
|
|
532
531
|
* timing-dependent, so it looks fine until a full-repo sweep runs against a
|
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/release.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Unified default-branch release workflow generation. */
|
|
2
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
2
3
|
import { Component } from "projen";
|
|
3
4
|
import { GithubWorkflow } from "projen/lib/github";
|
|
4
5
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
@@ -205,47 +206,52 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
|
|
|
205
206
|
"${{ github.event_name == 'workflow_dispatch' && inputs.expected_sha || '' }}",
|
|
206
207
|
DRY_RUN: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}",
|
|
207
208
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
209
|
+
// prettier-ignore
|
|
210
|
+
run: stringUtils.dedent(
|
|
211
|
+
// ============================================================================
|
|
212
|
+
/*bash*/`
|
|
213
|
+
if [ "$GITHUB_EVENT_NAME" = "push" ]; then
|
|
214
|
+
test "$GITHUB_REF_TYPE" = "branch"
|
|
215
|
+
test "$GITHUB_REF_NAME" = "${releaseBranch}"
|
|
216
|
+
RELEASE_SHA="$GITHUB_SHA"
|
|
217
|
+
test "$(git rev-parse HEAD)" = "$RELEASE_SHA"
|
|
218
|
+
RELEASE_VERSION="$(tr -d '\\r\\n' < VERSION)"
|
|
219
|
+
bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"
|
|
220
|
+
PREVIOUS_VERSION="$(git show "$RELEASE_SHA^:VERSION" | tr -d '\\r\\n')"
|
|
221
|
+
test "$PREVIOUS_VERSION" != "$RELEASE_VERSION"
|
|
222
|
+
RELEASE_TAG="${tagPrefix}$RELEASE_VERSION"
|
|
223
|
+
if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
|
|
224
|
+
git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
|
|
225
|
+
test "$(git cat-file -t "$RELEASE_TAG")" = "tag"
|
|
226
|
+
test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$RELEASE_SHA"
|
|
227
|
+
else
|
|
228
|
+
git fetch --force --tags origin
|
|
229
|
+
bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION" --prefix ${JSON.stringify(tagPrefix)} --assert-next
|
|
230
|
+
git config user.name "github-actions[bot]"
|
|
231
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
232
|
+
git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"
|
|
233
|
+
git push origin "refs/tags/$RELEASE_TAG"
|
|
234
|
+
fi
|
|
235
|
+
else
|
|
236
|
+
case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac
|
|
237
|
+
RELEASE_VERSION="\${RELEASE_TAG#${tagPrefix}}"
|
|
238
|
+
bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"
|
|
239
|
+
git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
|
|
240
|
+
test "$(git cat-file -t "$RELEASE_TAG")" = "tag"
|
|
241
|
+
RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"
|
|
242
|
+
test "$(git rev-parse HEAD)" = "$RELEASE_SHA"
|
|
243
|
+
test "$RELEASE_SHA" = "$EXPECTED_SHA"
|
|
244
|
+
if [ -n "\${{ inputs.source_run_id }}" ]; then
|
|
245
|
+
case "\${{ inputs.stage }}" in node|python) ;; *) exit 1 ;; esac
|
|
246
|
+
case "\${{ inputs.source_run_id }}" in *[!0-9]*|"") exit 1 ;; esac
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
250
|
+
echo "expected_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
|
|
251
|
+
echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
|
252
|
+
`
|
|
253
|
+
// ============================================================================
|
|
254
|
+
),
|
|
249
255
|
},
|
|
250
256
|
{
|
|
251
257
|
name: "Verify source artifact run",
|
|
@@ -256,15 +262,20 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
|
|
|
256
262
|
SOURCE_RUN_ID: "${{ inputs.source_run_id }}",
|
|
257
263
|
},
|
|
258
264
|
with: {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
// prettier-ignore
|
|
266
|
+
script: stringUtils.dedent(
|
|
267
|
+
// ============================================================================
|
|
268
|
+
/*js*/`
|
|
269
|
+
const run = await github.rest.actions.getWorkflowRun({
|
|
270
|
+
owner: context.repo.owner,
|
|
271
|
+
repo: context.repo.repo,
|
|
272
|
+
run_id: Number(process.env.SOURCE_RUN_ID),
|
|
273
|
+
});
|
|
274
|
+
if (run.data.path !== ".github/workflows/release.yml") core.setFailed("Source run is not release.yml");
|
|
275
|
+
if (run.data.head_sha !== process.env.EXPECTED_SHA) core.setFailed("Source run commit does not match the release tag");
|
|
276
|
+
`
|
|
277
|
+
// ============================================================================
|
|
278
|
+
),
|
|
268
279
|
},
|
|
269
280
|
},
|
|
270
281
|
],
|
|
@@ -422,44 +433,49 @@ function independentReleasePleaseJob(project: DBXToolsJavaScriptProject, branch:
|
|
|
422
433
|
RELEASE_PRS: "${{ steps.release.outputs.prs }}",
|
|
423
434
|
},
|
|
424
435
|
shell: "bash",
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
"
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
"
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"
|
|
461
|
-
|
|
462
|
-
|
|
436
|
+
// prettier-ignore
|
|
437
|
+
run: stringUtils.dedent(
|
|
438
|
+
// ============================================================================
|
|
439
|
+
/*bash*/`
|
|
440
|
+
BRANCH="$(jq -r '.[0].headBranchName' <<<"$RELEASE_PRS")"
|
|
441
|
+
test -n "$BRANCH" && test "$BRANCH" != "null"
|
|
442
|
+
git fetch origin "$BRANCH"
|
|
443
|
+
git switch --force-create "$BRANCH" "origin/$BRANCH"
|
|
444
|
+
bun install
|
|
445
|
+
bunx projen
|
|
446
|
+
bun node_modules/@dbx-tools/projen/tasks/version-check.ts
|
|
447
|
+
bun node_modules/@dbx-tools/projen/tasks/release-plan.ts --base-ref origin/${branch}
|
|
448
|
+
bun node_modules/@dbx-tools/projen/tasks/release-summary-units.ts --from-ref origin/${branch}
|
|
449
|
+
if ! git diff --quiet || test -n "$(git ls-files --others --exclude-standard)"; then
|
|
450
|
+
git config user.name "github-actions[bot]"
|
|
451
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
452
|
+
git add -A
|
|
453
|
+
git commit -m "chore: reconcile release metadata"
|
|
454
|
+
git push origin "HEAD:$BRANCH"
|
|
455
|
+
fi
|
|
456
|
+
MERGED="false"
|
|
457
|
+
for ATTEMPT in $(seq 1 15); do
|
|
458
|
+
STATE="$(gh pr view "$BRANCH" --json state,mergeable --jq '[.state,.mergeable] | join(" ")')"
|
|
459
|
+
if [[ "$STATE" == MERGED* ]]; then MERGED="true"; break; fi
|
|
460
|
+
if [ "$STATE" = "OPEN CONFLICTING" ]; then
|
|
461
|
+
echo "::error::release pull request has merge conflicts"
|
|
462
|
+
exit 1
|
|
463
|
+
fi
|
|
464
|
+
if [ "$STATE" = "OPEN MERGEABLE" ] && gh pr merge "$BRANCH" --merge; then
|
|
465
|
+
MERGED="true"
|
|
466
|
+
break
|
|
467
|
+
fi
|
|
468
|
+
echo "release pull request is not mergeable yet (attempt $ATTEMPT/15)"
|
|
469
|
+
sleep 2
|
|
470
|
+
done
|
|
471
|
+
if [ "$MERGED" != "true" ]; then
|
|
472
|
+
echo "::error::release pull request did not become mergeable"
|
|
473
|
+
exit 1
|
|
474
|
+
fi
|
|
475
|
+
gh workflow run release.yml --ref "$RELEASE_BRANCH" -f automatic=true
|
|
476
|
+
`
|
|
477
|
+
// ============================================================================
|
|
478
|
+
),
|
|
463
479
|
},
|
|
464
480
|
],
|
|
465
481
|
};
|
|
@@ -499,22 +515,27 @@ function configureReleaseRequestWorkflow(
|
|
|
499
515
|
name: "Read release request",
|
|
500
516
|
id: "request",
|
|
501
517
|
shell: "bash",
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
"
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
+
// prettier-ignore
|
|
519
|
+
run: stringUtils.dedent(
|
|
520
|
+
// ============================================================================
|
|
521
|
+
/*bash*/`
|
|
522
|
+
MESSAGE="$(git log -1 --format=%B)"
|
|
523
|
+
if ! grep -q "^Release-Request: true$" <<<"$MESSAGE"; then
|
|
524
|
+
echo "requested=false" >> "$GITHUB_OUTPUT"
|
|
525
|
+
exit 0
|
|
526
|
+
fi
|
|
527
|
+
git fetch origin ${JSON.stringify(baseBranch)}
|
|
528
|
+
if git diff --quiet "origin/${baseBranch}...HEAD"; then
|
|
529
|
+
echo "requested=false" >> "$GITHUB_OUTPUT"
|
|
530
|
+
exit 0
|
|
531
|
+
fi
|
|
532
|
+
echo "requested=true" >> "$GITHUB_OUTPUT"
|
|
533
|
+
echo "branch=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
|
|
534
|
+
echo "notes_path=$(sed -n 's/^Release-Notes-Path: //p' <<<"$MESSAGE" | tail -1)" >> "$GITHUB_OUTPUT"
|
|
535
|
+
echo "title=$(git log -1 --skip=1 --format=%s)" >> "$GITHUB_OUTPUT"
|
|
536
|
+
`
|
|
537
|
+
// ============================================================================
|
|
538
|
+
),
|
|
518
539
|
},
|
|
519
540
|
{
|
|
520
541
|
name: "Create, merge, and release source pull request",
|
|
@@ -527,20 +548,25 @@ function configureReleaseRequestWorkflow(
|
|
|
527
548
|
NOTES_PATH: "${{ steps.request.outputs.notes_path }}",
|
|
528
549
|
},
|
|
529
550
|
shell: "bash",
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
"
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
551
|
+
// prettier-ignore
|
|
552
|
+
run: stringUtils.dedent(
|
|
553
|
+
// ============================================================================
|
|
554
|
+
/*bash*/`
|
|
555
|
+
test -f "$NOTES_PATH"
|
|
556
|
+
NOTES="$(cat "$NOTES_PATH")"
|
|
557
|
+
BODY="$(printf '## Release notes\\n\\n%s\\n' "$NOTES")"
|
|
558
|
+
PR="$(gh pr list --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number // empty')"
|
|
559
|
+
if [ -n "$PR" ]; then
|
|
560
|
+
gh pr edit "$PR" --title "$TITLE" --body "$BODY"
|
|
561
|
+
else
|
|
562
|
+
gh pr create --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --title "$TITLE" --body "$BODY"
|
|
563
|
+
PR="$(gh pr list --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number')"
|
|
564
|
+
fi
|
|
565
|
+
gh pr merge "$PR" --merge
|
|
566
|
+
gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref "$BASE_BRANCH" -f automatic=true
|
|
567
|
+
`
|
|
568
|
+
// ============================================================================
|
|
569
|
+
),
|
|
544
570
|
},
|
|
545
571
|
],
|
|
546
572
|
});
|
|
@@ -672,20 +698,25 @@ function independentReleaseNotesJob(project: DBXToolsJavaScriptProject): Job {
|
|
|
672
698
|
{
|
|
673
699
|
name: "Remove consumed release notes",
|
|
674
700
|
shell: "bash",
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
"
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
701
|
+
// prettier-ignore
|
|
702
|
+
run: stringUtils.dedent(
|
|
703
|
+
// ============================================================================
|
|
704
|
+
/*bash*/`
|
|
705
|
+
git fetch origin ${JSON.stringify(projectReleaseBranch(project))}
|
|
706
|
+
if [ "$(git rev-parse origin/${projectReleaseBranch(project)})" != "$(git rev-parse HEAD)" ]; then
|
|
707
|
+
echo "::warning::main advanced before release-note cleanup; leaving temporary notes for the next release"
|
|
708
|
+
exit 0
|
|
709
|
+
fi
|
|
710
|
+
git rm -r --ignore-unmatch .release-notes
|
|
711
|
+
if ! git diff --cached --quiet; then
|
|
712
|
+
git config user.name "github-actions[bot]"
|
|
713
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
714
|
+
git commit -m "chore: remove consumed release notes"
|
|
715
|
+
git push origin "HEAD:${projectReleaseBranch(project)}"
|
|
716
|
+
fi
|
|
717
|
+
`
|
|
718
|
+
// ============================================================================
|
|
719
|
+
),
|
|
689
720
|
},
|
|
690
721
|
],
|
|
691
722
|
};
|
|
@@ -769,44 +800,49 @@ function addIndependentBranchSyncJob(
|
|
|
769
800
|
name: `Safely sync ${branch}`,
|
|
770
801
|
shell: "bash",
|
|
771
802
|
env: { SOURCE_BRANCH: branch, RELEASE_BRANCH: releaseBranch },
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
"
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
803
|
+
// prettier-ignore
|
|
804
|
+
run: stringUtils.dedent(
|
|
805
|
+
// ============================================================================
|
|
806
|
+
/*bash*/`
|
|
807
|
+
if ! git ls-remote --exit-code --heads origin "refs/heads/$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
808
|
+
echo "source branch $SOURCE_BRANCH no longer exists; nothing to sync"
|
|
809
|
+
exit 0
|
|
810
|
+
fi
|
|
811
|
+
git fetch origin "refs/heads/$SOURCE_BRANCH:refs/remotes/origin/$SOURCE_BRANCH"
|
|
812
|
+
git fetch origin "refs/heads/$RELEASE_BRANCH:refs/remotes/origin/$RELEASE_BRANCH"
|
|
813
|
+
if git merge-base --is-ancestor "origin/$SOURCE_BRANCH" "origin/$RELEASE_BRANCH"; then
|
|
814
|
+
git push origin "refs/remotes/origin/$RELEASE_BRANCH:refs/heads/$SOURCE_BRANCH"
|
|
815
|
+
elif git merge-base --is-ancestor "origin/$RELEASE_BRANCH" "origin/$SOURCE_BRANCH"; then
|
|
816
|
+
echo "source branch $SOURCE_BRANCH already contains released main"
|
|
817
|
+
else
|
|
818
|
+
git config user.name "github-actions[bot]"
|
|
819
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
820
|
+
git switch --force-create "$SOURCE_BRANCH" "origin/$SOURCE_BRANCH"
|
|
821
|
+
if git merge --no-ff --no-edit "origin/$RELEASE_BRANCH"; then
|
|
822
|
+
git push origin "HEAD:refs/heads/$SOURCE_BRANCH"
|
|
823
|
+
else
|
|
824
|
+
SAFE_GENERATED="true"
|
|
825
|
+
while IFS= read -r FILE; do
|
|
826
|
+
case "$FILE" in
|
|
827
|
+
.projen/release-units.json|.release-please-manifest.json|.release-units/*/source.json|.release-units/*/version.txt|.release-units/*/CHANGELOG.md|Cargo.lock|*/Cargo.toml|*/package.json|*/pyproject.toml|*/index.ts) ;;
|
|
828
|
+
*) SAFE_GENERATED="false" ;;
|
|
829
|
+
esac
|
|
830
|
+
done < <(git diff --name-only --diff-filter=U)
|
|
831
|
+
if [ "$SAFE_GENERATED" = "true" ] && bunx projen; then
|
|
832
|
+
git add -A
|
|
833
|
+
if [ -z "$(git diff --name-only --diff-filter=U)" ]; then
|
|
834
|
+
git commit --no-edit
|
|
835
|
+
git push origin "HEAD:refs/heads/$SOURCE_BRANCH"
|
|
836
|
+
exit 0
|
|
837
|
+
fi
|
|
838
|
+
fi
|
|
839
|
+
git merge --abort
|
|
840
|
+
echo "::warning::source branch $SOURCE_BRANCH has handwritten or unresolved conflicts with released main; leaving it untouched"
|
|
841
|
+
fi
|
|
842
|
+
fi
|
|
843
|
+
`
|
|
844
|
+
// ============================================================================
|
|
845
|
+
),
|
|
810
846
|
},
|
|
811
847
|
],
|
|
812
848
|
});
|
package/tasks/sync.ts
CHANGED
|
@@ -95,9 +95,9 @@ if (!process.argv.includes("--watch")) {
|
|
|
95
95
|
* re-emitting it is how we say "stop" in a language the supervisor understands.
|
|
96
96
|
*
|
|
97
97
|
* Killing is asynchronous (concurrently shells out to `ps` to walk each process tree)
|
|
98
|
-
* and can outlive `result
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* and can outlive `result`. The timer keeps the process open until the kills
|
|
99
|
+
* land, which prevents orphaned watchers after SIGTERM. It also stops waiting
|
|
100
|
+
* if one of them wedges.
|
|
101
101
|
*/
|
|
102
102
|
function stop(): void {
|
|
103
103
|
if (stopping) return;
|