@dbx-tools/projen 0.9.152 → 0.9.154
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 +31 -12
- package/index.ts +3 -2
- package/package.json +7 -6
- package/src/appkit-workspace.ts +41 -6
- package/src/bun-app.ts +7 -1
- package/src/generated.ts +16 -7
- package/src/packages.ts +6 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/project-js.ts +20 -4
- package/src/project-py.ts +52 -19
- package/src/release-context.ts +4 -0
- package/src/release.ts +131 -11
- package/src/tsconfig.ts +9 -2
- package/src/workspace-manifest.ts +10 -1
- package/tasks/appkit-deploy.ts +192 -17
- package/tasks/publish-npm.ts +5 -3
- package/tasks/pypi-trusted-publisher-instructions.ts +54 -0
- package/tasks/python-node-bindings.ts +11 -7
- package/tasks/verify-publications.ts +269 -0
package/src/release.ts
CHANGED
|
@@ -16,12 +16,17 @@ import { GithubWorkflow } from "projen/lib/github";
|
|
|
16
16
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
17
17
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
18
18
|
import { projectReleaseBranch, taskCommand, type DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
PUBLIC_NPM_REGISTRY_URL,
|
|
21
|
+
RELEASE_VERSION,
|
|
22
|
+
RELEASE_WORKFLOW_FILE,
|
|
23
|
+
} from "./release-context.ts";
|
|
20
24
|
import type { ReleaseStepSelection } from "./release-options.ts";
|
|
21
25
|
|
|
22
26
|
const NODE_VERSION = "24";
|
|
23
27
|
const NPM_VERSION = "11.4.2";
|
|
24
|
-
const
|
|
28
|
+
const PUBLICATION_VERIFIER_ARTIFACT = "release-publication-verifier";
|
|
29
|
+
const PUBLICATION_VERIFIER_JOB = "verify-publications";
|
|
25
30
|
const releaseTagPrefixes = new WeakMap<DBXToolsJavaScriptProject, string>();
|
|
26
31
|
const releaseWorkflows = new WeakMap<DBXToolsJavaScriptProject, GithubWorkflow>();
|
|
27
32
|
|
|
@@ -95,7 +100,7 @@ export function nodeReleaseSetupSteps(): readonly JobStep[] {
|
|
|
95
100
|
uses: github.ActionRefs.ACTIONS_SETUP_NODE,
|
|
96
101
|
with: {
|
|
97
102
|
"node-version": NODE_VERSION,
|
|
98
|
-
"registry-url":
|
|
103
|
+
"registry-url": PUBLIC_NPM_REGISTRY_URL,
|
|
99
104
|
"package-manager-cache": false,
|
|
100
105
|
},
|
|
101
106
|
},
|
|
@@ -114,19 +119,14 @@ export function uvSetupStep(): JobStep {
|
|
|
114
119
|
function refreshDocsRegistryDependencies(workflow: GithubWorkflow): void {
|
|
115
120
|
const docs = workflow.getJob("deploy-docs") as Job | undefined;
|
|
116
121
|
if (!docs) return;
|
|
117
|
-
|
|
118
|
-
(name) => name === "publish-node" || name.startsWith("publish-pypi-"),
|
|
119
|
-
);
|
|
120
|
-
if (registryJobs.length > 0) {
|
|
122
|
+
if (workflow.getJob(PUBLICATION_VERIFIER_JOB)) {
|
|
121
123
|
workflow.updateJob("deploy-docs", {
|
|
122
124
|
...docs,
|
|
123
|
-
needs: ["build-release",
|
|
125
|
+
needs: ["build-release", PUBLICATION_VERIFIER_JOB],
|
|
124
126
|
if: releaseCondition([
|
|
125
127
|
"needs['build-release'].result == 'success'",
|
|
126
128
|
"needs['build-release'].outputs.docs == 'true'",
|
|
127
|
-
|
|
128
|
-
(name) => `needs['${name}'].result == 'success' || needs['${name}'].result == 'skipped'`,
|
|
129
|
-
),
|
|
129
|
+
`needs['${PUBLICATION_VERIFIER_JOB}'].result == 'success' || needs['${PUBLICATION_VERIFIER_JOB}'].result == 'skipped'`,
|
|
130
130
|
]),
|
|
131
131
|
});
|
|
132
132
|
}
|
|
@@ -140,6 +140,107 @@ export function refreshReleaseDocsDependencies(project: DBXToolsJavaScriptProjec
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
function publicationRegistryJobs(workflow: GithubWorkflow): string[] {
|
|
144
|
+
return Object.keys(workflow.jobs).filter(
|
|
145
|
+
(name) => name === "publish-node" || name.startsWith("publish-pypi-"),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Keep one public availability barrier behind every configured registry publisher. */
|
|
150
|
+
export function refreshPublicationVerification(project: DBXToolsJavaScriptProject): void {
|
|
151
|
+
const workflow = releaseWorkflows.get(project);
|
|
152
|
+
if (!workflow) return;
|
|
153
|
+
const registryJobs = publicationRegistryJobs(workflow);
|
|
154
|
+
if (registryJobs.length === 0) return;
|
|
155
|
+
const hasNpm = registryJobs.includes("publish-node");
|
|
156
|
+
const hasPypi = registryJobs.some((name) => name.startsWith("publish-pypi-"));
|
|
157
|
+
const selected = [
|
|
158
|
+
...(hasNpm ? ["needs['build-release'].outputs.npm == 'true'"] : []),
|
|
159
|
+
...(hasPypi ? ["needs['build-release'].outputs.pypi == 'true'"] : []),
|
|
160
|
+
].join(" || ");
|
|
161
|
+
const prerequisites = registryJobs.map((name) => {
|
|
162
|
+
const output = name === "publish-node" ? "npm" : "pypi";
|
|
163
|
+
return `needs['build-release'].outputs.${output} != 'true' || needs['${name}'].result == 'success'`;
|
|
164
|
+
});
|
|
165
|
+
const job: Job = {
|
|
166
|
+
if: releaseCondition([
|
|
167
|
+
"needs['build-release'].result == 'success'",
|
|
168
|
+
selected,
|
|
169
|
+
...prerequisites,
|
|
170
|
+
]),
|
|
171
|
+
needs: ["build-release", ...registryJobs],
|
|
172
|
+
runsOn: ["ubuntu-latest"],
|
|
173
|
+
permissions: { contents: JobPermission.READ },
|
|
174
|
+
timeoutMinutes: 15,
|
|
175
|
+
steps: [
|
|
176
|
+
{
|
|
177
|
+
name: "Download publication verifier",
|
|
178
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
179
|
+
with: { name: PUBLICATION_VERIFIER_ARTIFACT, path: ".release/verifier" },
|
|
180
|
+
},
|
|
181
|
+
...(hasNpm
|
|
182
|
+
? [
|
|
183
|
+
{
|
|
184
|
+
name: "Download npm archives",
|
|
185
|
+
if: "${{ needs.build-release.outputs.npm == 'true' }}",
|
|
186
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
187
|
+
with: { name: "release-npm", path: ".release/npm" },
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
: []),
|
|
191
|
+
...(hasPypi
|
|
192
|
+
? [
|
|
193
|
+
{
|
|
194
|
+
name: "Download Python distributions",
|
|
195
|
+
if: "${{ needs.build-release.outputs.pypi == 'true' }}",
|
|
196
|
+
uses: github.ActionRefs.ACTIONS_DOWNLOAD_ARTIFACT,
|
|
197
|
+
with: {
|
|
198
|
+
pattern: "release-python-*",
|
|
199
|
+
path: ".release/python",
|
|
200
|
+
"merge-multiple": true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
]
|
|
204
|
+
: []),
|
|
205
|
+
{
|
|
206
|
+
name: "Setup Bun",
|
|
207
|
+
uses: github.ActionRefs.OVEN_SH_SETUP_BUN,
|
|
208
|
+
with: { "bun-version": BUN_VERSION },
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "Wait for public package availability",
|
|
212
|
+
shell: "bash",
|
|
213
|
+
env: {
|
|
214
|
+
RELEASE_VERSION,
|
|
215
|
+
VERIFY_NPM: hasNpm ? "${{ needs.build-release.outputs.npm }}" : "false",
|
|
216
|
+
VERIFY_PYPI: hasPypi ? "${{ needs.build-release.outputs.pypi }}" : "false",
|
|
217
|
+
},
|
|
218
|
+
// prettier-ignore
|
|
219
|
+
run: stringUtils.dedent(
|
|
220
|
+
// ============================================================================
|
|
221
|
+
/*bash*/`
|
|
222
|
+
args=(--version "$RELEASE_VERSION")
|
|
223
|
+
if [[ "$VERIFY_NPM" == "true" ]]; then
|
|
224
|
+
args+=(--npm-directory .release/npm)
|
|
225
|
+
fi
|
|
226
|
+
if [[ "$VERIFY_PYPI" == "true" ]]; then
|
|
227
|
+
args+=(--pypi-directory .release/python)
|
|
228
|
+
fi
|
|
229
|
+
bun .release/verifier/verify-publications.mjs "\${args[@]}"
|
|
230
|
+
`
|
|
231
|
+
// ============================================================================
|
|
232
|
+
),
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
if (workflow.getJob(PUBLICATION_VERIFIER_JOB)) {
|
|
237
|
+
workflow.updateJob(PUBLICATION_VERIFIER_JOB, job);
|
|
238
|
+
} else {
|
|
239
|
+
workflow.addJob(PUBLICATION_VERIFIER_JOB, job);
|
|
240
|
+
}
|
|
241
|
+
refreshDocsRegistryDependencies(workflow);
|
|
242
|
+
}
|
|
243
|
+
|
|
143
244
|
function releaseBuildJob(
|
|
144
245
|
project: DBXToolsJavaScriptProject,
|
|
145
246
|
tagPrefix: string,
|
|
@@ -229,6 +330,24 @@ function releaseBuildJob(
|
|
|
229
330
|
with: { name: "release-npm", path: ".release/npm", "if-no-files-found": "error" },
|
|
230
331
|
},
|
|
231
332
|
])),
|
|
333
|
+
{
|
|
334
|
+
name: "Build publication verifier",
|
|
335
|
+
if: "${{ success() && (steps.release.outputs.npm == 'true' || steps.release.outputs.pypi == 'true') }}",
|
|
336
|
+
run: [
|
|
337
|
+
"mkdir -p .release/verifier",
|
|
338
|
+
"bun build node_modules/@dbx-tools/projen/tasks/verify-publications.ts --target=bun --outfile=.release/verifier/verify-publications.mjs",
|
|
339
|
+
].join("\n"),
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "Upload publication verifier",
|
|
343
|
+
if: "${{ success() && (steps.release.outputs.npm == 'true' || steps.release.outputs.pypi == 'true') }}",
|
|
344
|
+
uses: github.ActionRefs.ACTIONS_UPLOAD_ARTIFACT,
|
|
345
|
+
with: {
|
|
346
|
+
name: PUBLICATION_VERIFIER_ARTIFACT,
|
|
347
|
+
path: ".release/verifier/verify-publications.mjs",
|
|
348
|
+
"if-no-files-found": "error",
|
|
349
|
+
},
|
|
350
|
+
},
|
|
232
351
|
...(options.docs
|
|
233
352
|
? releaseBuildSteps("docs", [
|
|
234
353
|
...options.docs.prepareSteps,
|
|
@@ -356,6 +475,7 @@ export class DBXToolsRelease extends Component {
|
|
|
356
475
|
if (options.nodeRelease !== false) {
|
|
357
476
|
workflow.addJob("publish-node", nodePublishJob());
|
|
358
477
|
}
|
|
478
|
+
refreshPublicationVerification(project);
|
|
359
479
|
if (options.docs) {
|
|
360
480
|
addDocsJobs(workflow);
|
|
361
481
|
}
|
package/src/tsconfig.ts
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Component, type Project, javascript, typescript } from "projen";
|
|
5
5
|
|
|
6
|
+
/** Consumer overrides for the repository-root TypeScript program. */
|
|
7
|
+
export interface DBXToolsRootTsconfigOptions {
|
|
8
|
+
readonly compilerOptions?: javascript.TypeScriptCompilerOptions;
|
|
9
|
+
readonly include?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
/**
|
|
7
13
|
* Compiler options for the ROOT program only (`.projenrc.ts` + the engine as
|
|
8
14
|
* seen from the root). Each package owns its own projen-generated
|
|
@@ -40,7 +46,7 @@ export class DBXToolsRootTsconfig extends Component {
|
|
|
40
46
|
/** Root program config inherited by the generated Projen config. */
|
|
41
47
|
readonly config?: javascript.TypescriptConfig;
|
|
42
48
|
|
|
43
|
-
constructor(scope: Project) {
|
|
49
|
+
constructor(scope: Project, options: DBXToolsRootTsconfigOptions = {}) {
|
|
44
50
|
super(scope);
|
|
45
51
|
|
|
46
52
|
if (scope instanceof typescript.TypeScriptProject) {
|
|
@@ -57,8 +63,9 @@ export class DBXToolsRootTsconfig extends Component {
|
|
|
57
63
|
lib: ["ESNext"],
|
|
58
64
|
types: ["node", "bun"],
|
|
59
65
|
jsx: javascript.TypeScriptJsxMode.REACT_JSX,
|
|
66
|
+
...options.compilerOptions,
|
|
60
67
|
},
|
|
61
|
-
include: [".projenrc.ts", "test/**/*.ts", "test/**/*.tsx"],
|
|
68
|
+
include: [".projenrc.ts", "test/**/*.ts", "test/**/*.tsx", ...(options.include ?? [])],
|
|
62
69
|
exclude: ["node_modules", "**/dist", "**/node_modules"],
|
|
63
70
|
});
|
|
64
71
|
}
|
|
@@ -43,6 +43,10 @@ export function materializeWorkspaceManifest(
|
|
|
43
43
|
workspace.catalog && typeof workspace.catalog === "object"
|
|
44
44
|
? (workspace.catalog as Readonly<Record<string, unknown>>)
|
|
45
45
|
: {};
|
|
46
|
+
const overrides =
|
|
47
|
+
workspace.overrides && typeof workspace.overrides === "object"
|
|
48
|
+
? (workspace.overrides as Readonly<Record<string, unknown>>)
|
|
49
|
+
: {};
|
|
46
50
|
if (typeof version !== "string" || !version) {
|
|
47
51
|
throw new Error("Workspace manifest has no version");
|
|
48
52
|
}
|
|
@@ -52,7 +56,7 @@ export function materializeWorkspaceManifest(
|
|
|
52
56
|
manifest[field] = Object.fromEntries(
|
|
53
57
|
Object.entries(dependencies).map(([name, value]) => [
|
|
54
58
|
name,
|
|
55
|
-
materializeDependency(name, value, version, catalog),
|
|
59
|
+
materializeDependency(name, value, version, catalog, overrides),
|
|
56
60
|
]),
|
|
57
61
|
);
|
|
58
62
|
}
|
|
@@ -134,7 +138,12 @@ function materializeDependency(
|
|
|
134
138
|
value: unknown,
|
|
135
139
|
version: string,
|
|
136
140
|
catalog: Readonly<Record<string, unknown>>,
|
|
141
|
+
overrides: Readonly<Record<string, unknown>>,
|
|
137
142
|
): unknown {
|
|
143
|
+
const override = overrides[name];
|
|
144
|
+
if (typeof override === "string" && override && !override.startsWith("$")) {
|
|
145
|
+
return override;
|
|
146
|
+
}
|
|
138
147
|
if (value === "catalog:") {
|
|
139
148
|
const resolved = catalog[name];
|
|
140
149
|
if (typeof resolved !== "string" || !resolved) {
|
package/tasks/appkit-deploy.ts
CHANGED
|
@@ -1,16 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
2
|
/** Deploy an AppKit workspace through npm-compatible temporary manifests. */
|
|
3
|
+
import { existsSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
3
5
|
import { profile } from "@dbx-tools/auth";
|
|
6
|
+
import { serializeArgs } from "@dbx-tools/cli-args";
|
|
4
7
|
import * as exec from "@dbx-tools/core/exec";
|
|
5
|
-
import {
|
|
8
|
+
import { errorUtils, log } from "@dbx-tools/shared-core";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
|
|
6
11
|
import { withStagedWorkspaceManifests } from "../src/workspace-manifest.ts";
|
|
7
12
|
|
|
13
|
+
const logger = log.logger("projen:appkit-deploy");
|
|
14
|
+
|
|
15
|
+
export const AppKitDeployContextOptionsSchema = z.object({
|
|
16
|
+
target: z
|
|
17
|
+
.string()
|
|
18
|
+
.trim()
|
|
19
|
+
.min(1)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Databricks bundle target")
|
|
22
|
+
.meta({ env: [], short: "t" }),
|
|
23
|
+
profile: z.string().trim().min(1).optional().describe("Databricks CLI profile").meta({
|
|
24
|
+
env: [],
|
|
25
|
+
helpDefault: false,
|
|
26
|
+
short: "p",
|
|
27
|
+
}),
|
|
28
|
+
var: z
|
|
29
|
+
.array(z.string().trim().min(1))
|
|
30
|
+
.default([])
|
|
31
|
+
.describe("Databricks bundle variable assignment")
|
|
32
|
+
.meta({ env: [] }),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const AppKitDeployOptionsSchema = AppKitDeployContextOptionsSchema.extend({
|
|
36
|
+
autoApprove: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.default(false)
|
|
39
|
+
.describe("Skip interactive deployment approval")
|
|
40
|
+
.meta({ env: [] }),
|
|
41
|
+
failOnActiveRuns: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.default(true)
|
|
44
|
+
.describe("Fail when bundle jobs or pipelines are active")
|
|
45
|
+
.meta({ env: [] }),
|
|
46
|
+
skipTests: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.default(true)
|
|
49
|
+
.describe("Skip Databricks CLI tests after the Bun validation")
|
|
50
|
+
.meta({ env: [] }),
|
|
51
|
+
skipValidation: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.default(true)
|
|
54
|
+
.describe("Skip Databricks CLI npm validation after the Bun validation")
|
|
55
|
+
.meta({ env: [] }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type AppKitDeployOptions = z.output<typeof AppKitDeployOptionsSchema>;
|
|
59
|
+
|
|
60
|
+
interface DeployCommandResult {
|
|
61
|
+
readonly exitCode: number;
|
|
62
|
+
readonly stderr?: string;
|
|
63
|
+
readonly stdout?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
8
66
|
/** Effects accepted by the AppKit deploy task for focused tests. */
|
|
9
67
|
export interface AppKitDeployDependencies {
|
|
10
68
|
readonly environment?: NodeJS.ProcessEnv;
|
|
69
|
+
readonly logs?: (args: readonly string[], cwd: string) => Promise<DeployCommandResult>;
|
|
70
|
+
readonly plan?: (args: readonly string[], cwd: string) => Promise<DeployCommandResult>;
|
|
11
71
|
readonly root?: string;
|
|
12
72
|
readonly resolveProfile?: () => string | undefined;
|
|
13
|
-
readonly run?: (args: readonly string[], cwd: string) => Promise<
|
|
73
|
+
readonly run?: (args: readonly string[], cwd: string) => Promise<DeployCommandResult>;
|
|
74
|
+
readonly validate?: (args: readonly string[], cwd: string) => Promise<DeployCommandResult>;
|
|
14
75
|
}
|
|
15
76
|
|
|
16
77
|
/** Run Databricks Apps deploy while generated workspace manifests are materialized. */
|
|
@@ -19,7 +80,42 @@ export async function deployApp(
|
|
|
19
80
|
dependencies: AppKitDeployDependencies = {},
|
|
20
81
|
): Promise<void> {
|
|
21
82
|
const root = dependencies.root ?? process.cwd();
|
|
22
|
-
const
|
|
83
|
+
const options = await resolveDeployOptions(args, dependencies);
|
|
84
|
+
const context = AppKitDeployContextOptionsSchema.parse({
|
|
85
|
+
target: options.target,
|
|
86
|
+
profile: options.profile,
|
|
87
|
+
var: options.var,
|
|
88
|
+
});
|
|
89
|
+
const contextArgs = serializeArgs(context);
|
|
90
|
+
const deployArgs = serializeArgs({
|
|
91
|
+
...context,
|
|
92
|
+
...(options.autoApprove ? { autoApprove: true } : {}),
|
|
93
|
+
...(options.failOnActiveRuns ? { failOnActiveRuns: true } : {}),
|
|
94
|
+
...(options.skipTests ? { skipTests: true } : {}),
|
|
95
|
+
...(options.skipValidation ? { skipValidation: true } : {}),
|
|
96
|
+
});
|
|
97
|
+
const validate =
|
|
98
|
+
dependencies.validate ??
|
|
99
|
+
(dependencies.run
|
|
100
|
+
? undefined
|
|
101
|
+
: (validateArgs: readonly string[], cwd: string) =>
|
|
102
|
+
exec.spawn(
|
|
103
|
+
"databricks",
|
|
104
|
+
["bundle", "validate", ...serializeArgs({ strict: true }), ...validateArgs],
|
|
105
|
+
{
|
|
106
|
+
cwd,
|
|
107
|
+
env: dependencies.environment ?? process.env,
|
|
108
|
+
stdin: "ignore",
|
|
109
|
+
stdout: "inherit",
|
|
110
|
+
stderr: "inherit",
|
|
111
|
+
},
|
|
112
|
+
));
|
|
113
|
+
if (validate) {
|
|
114
|
+
const validation = await validate(contextArgs, root);
|
|
115
|
+
if (validation.exitCode !== 0) {
|
|
116
|
+
throw new Error(`databricks bundle validate exited with code ${validation.exitCode}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
23
119
|
const run =
|
|
24
120
|
dependencies.run ??
|
|
25
121
|
((deployArgs: readonly string[], cwd: string) =>
|
|
@@ -31,40 +127,119 @@ export async function deployApp(
|
|
|
31
127
|
stderr: "inherit",
|
|
32
128
|
}));
|
|
33
129
|
await withStagedWorkspaceManifests(root, async (stage) => {
|
|
130
|
+
await reconcileStagedBundleState(stage, contextArgs, options.target, dependencies);
|
|
34
131
|
const result = await run(deployArgs, stage);
|
|
35
132
|
if (result.exitCode !== 0) {
|
|
133
|
+
await printAppLogs(contextArgs, stage, dependencies);
|
|
36
134
|
throw new Error(`databricks apps deploy exited with code ${result.exitCode}`);
|
|
37
135
|
}
|
|
38
136
|
});
|
|
39
137
|
}
|
|
40
138
|
|
|
41
|
-
function
|
|
139
|
+
async function reconcileStagedBundleState(
|
|
140
|
+
stage: string,
|
|
141
|
+
contextArgs: readonly string[],
|
|
142
|
+
target: string | undefined,
|
|
143
|
+
dependencies: AppKitDeployDependencies,
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
const plan =
|
|
146
|
+
dependencies.plan ??
|
|
147
|
+
(dependencies.run
|
|
148
|
+
? undefined
|
|
149
|
+
: (args: readonly string[], cwd: string) =>
|
|
150
|
+
Promise.resolve(
|
|
151
|
+
exec.spawnSync(
|
|
152
|
+
"databricks",
|
|
153
|
+
["bundle", "plan", ...serializeArgs({ quiet: true }), ...args],
|
|
154
|
+
{
|
|
155
|
+
cwd,
|
|
156
|
+
env: dependencies.environment ?? process.env,
|
|
157
|
+
stdin: "ignore",
|
|
158
|
+
stdout: "capture",
|
|
159
|
+
stderr: "capture",
|
|
160
|
+
},
|
|
161
|
+
),
|
|
162
|
+
));
|
|
163
|
+
if (!plan) return;
|
|
164
|
+
const result = await plan(contextArgs, stage);
|
|
165
|
+
if (result.exitCode === 0) return;
|
|
166
|
+
const output = `${result.stderr ?? ""}\n${result.stdout ?? ""}`.toLowerCase();
|
|
167
|
+
if (
|
|
168
|
+
!output.includes("lineage mismatch in state files") ||
|
|
169
|
+
!output.includes("remote direct state")
|
|
170
|
+
) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!target) return;
|
|
174
|
+
const state = join(stage, ".databricks", "bundle", target, "resources.json");
|
|
175
|
+
if (!existsSync(state)) return;
|
|
176
|
+
rmSync(state);
|
|
177
|
+
logger.warn("Removed stale staged bundle state", { target });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function printAppLogs(
|
|
181
|
+
contextArgs: readonly string[],
|
|
182
|
+
cwd: string,
|
|
183
|
+
dependencies: AppKitDeployDependencies,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
logger.error("Databricks Apps deployment failed; recent app logs follow");
|
|
186
|
+
const showLogs =
|
|
187
|
+
dependencies.logs ??
|
|
188
|
+
((args: readonly string[], logsCwd: string) =>
|
|
189
|
+
exec.spawn("databricks", ["apps", "logs", ...serializeArgs({ tailLines: 200 }), ...args], {
|
|
190
|
+
cwd: logsCwd,
|
|
191
|
+
env: dependencies.environment ?? process.env,
|
|
192
|
+
stdin: "ignore",
|
|
193
|
+
stdout: "inherit",
|
|
194
|
+
stderr: "inherit",
|
|
195
|
+
}));
|
|
196
|
+
try {
|
|
197
|
+
const result = await showLogs(contextArgs, cwd);
|
|
198
|
+
if (result.exitCode !== 0) {
|
|
199
|
+
logger.warn("Could not read Databricks App logs", { exitCode: result.exitCode });
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
logger.warn("Could not read Databricks App logs", {
|
|
203
|
+
message: errorUtils.toError(error).message,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function resolveDeployOptions(
|
|
42
209
|
args: readonly string[],
|
|
43
210
|
dependencies: AppKitDeployDependencies,
|
|
44
|
-
):
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
211
|
+
): Promise<AppKitDeployOptions> {
|
|
212
|
+
const parsed = await taskOptions(
|
|
213
|
+
taskCommand(
|
|
214
|
+
import.meta.url,
|
|
215
|
+
"Validate and deploy an AppKit workspace through Databricks Apps",
|
|
216
|
+
AppKitDeployOptionsSchema,
|
|
217
|
+
),
|
|
218
|
+
AppKitDeployOptionsSchema,
|
|
219
|
+
args,
|
|
49
220
|
);
|
|
221
|
+
const environment = dependencies.environment ?? process.env;
|
|
50
222
|
const hasEnvironmentAuth =
|
|
51
223
|
Boolean(environment.DATABRICKS_HOST) &&
|
|
52
224
|
(Boolean(environment.DATABRICKS_TOKEN) ||
|
|
53
225
|
(Boolean(environment.DATABRICKS_CLIENT_ID) && Boolean(environment.DATABRICKS_CLIENT_SECRET)));
|
|
226
|
+
const configuredProfile = environment.DATABRICKS_CONFIG_PROFILE ?? environment.DATABRICKS_PROFILE;
|
|
54
227
|
const resolvedProfile =
|
|
55
|
-
|
|
228
|
+
parsed.profile ??
|
|
229
|
+
configuredProfile ??
|
|
230
|
+
(hasEnvironmentAuth
|
|
56
231
|
? undefined
|
|
57
|
-
: (dependencies.resolveProfile ?? (() => profile.resolveProfile()?.name))();
|
|
58
|
-
if (!
|
|
232
|
+
: (dependencies.resolveProfile ?? (() => profile.resolveProfile()?.name))());
|
|
233
|
+
if (!hasEnvironmentAuth && !resolvedProfile) {
|
|
59
234
|
throw new Error(
|
|
60
235
|
"Select a Databricks profile with --profile or configure Databricks environment credentials",
|
|
61
236
|
);
|
|
62
237
|
}
|
|
63
|
-
return
|
|
64
|
-
...
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
238
|
+
return AppKitDeployOptionsSchema.parse({
|
|
239
|
+
...parsed,
|
|
240
|
+
target: parsed.target ?? environment.DATABRICKS_BUNDLE_TARGET,
|
|
241
|
+
profile: resolvedProfile,
|
|
242
|
+
});
|
|
68
243
|
}
|
|
69
244
|
|
|
70
245
|
await runTaskMain(import.meta, () => deployApp(process.argv.slice(2)));
|
package/tasks/publish-npm.ts
CHANGED
|
@@ -20,11 +20,11 @@ import { z } from "zod";
|
|
|
20
20
|
import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
|
|
21
21
|
import { TaskConcurrencyOptionSchema, TaskDryRunOptionSchema } from "./options.ts";
|
|
22
22
|
import { runTaskCommandAsync } from "../src/_task-command.ts";
|
|
23
|
+
import { PUBLIC_NPM_REGISTRY_URL } from "../src/release-context.ts";
|
|
23
24
|
import { materializeWorkspaceManifest } from "../src/workspace-manifest.ts";
|
|
24
25
|
|
|
25
26
|
export { materializeWorkspaceManifest } from "../src/workspace-manifest.ts";
|
|
26
27
|
|
|
27
|
-
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
28
28
|
const DEFAULT_PUBLISH_CONCURRENCY = 4;
|
|
29
29
|
const logger = log.logger("projen:publish-npm");
|
|
30
30
|
|
|
@@ -132,10 +132,12 @@ export function npmReleaseMatches(
|
|
|
132
132
|
export async function publishedNpmRelease(
|
|
133
133
|
name: string,
|
|
134
134
|
version: string,
|
|
135
|
-
registry = process.env.NPM_CONFIG_REGISTRY ??
|
|
135
|
+
registry = process.env.NPM_CONFIG_REGISTRY ?? PUBLIC_NPM_REGISTRY_URL,
|
|
136
|
+
signal?: AbortSignal,
|
|
136
137
|
): Promise<NpmReleaseIdentity | undefined> {
|
|
137
138
|
const response = await fetch(registryUrl(registry, name, version), {
|
|
138
139
|
headers: { accept: "application/json" },
|
|
140
|
+
signal,
|
|
139
141
|
});
|
|
140
142
|
if (response.status === 404) return undefined;
|
|
141
143
|
if (!response.ok) {
|
|
@@ -152,7 +154,7 @@ export async function publishedNpmRelease(
|
|
|
152
154
|
}
|
|
153
155
|
let contentDigest: string | undefined;
|
|
154
156
|
if (metadata.dist?.tarball) {
|
|
155
|
-
const archive = await fetch(metadata.dist.tarball);
|
|
157
|
+
const archive = await fetch(metadata.dist.tarball, { signal });
|
|
156
158
|
if (!archive.ok) {
|
|
157
159
|
throw new Error(`npm tarball lookup failed for ${name}@${version}: ${archive.status}`);
|
|
158
160
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Parse and print generated PyPI trusted-publisher setup instructions. */
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { taskCommand, taskOptions } from "./cli.ts";
|
|
4
|
+
|
|
5
|
+
export const PypiTrustedPublisherInstructionsOptionsSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
secretFile: z
|
|
8
|
+
.string()
|
|
9
|
+
.trim()
|
|
10
|
+
.min(1)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Credential file used only when browser authentication is required")
|
|
13
|
+
.meta({ env: [], helpDefault: false, short: false }),
|
|
14
|
+
})
|
|
15
|
+
.describe("Options for rendering PyPI trusted-publisher setup instructions");
|
|
16
|
+
|
|
17
|
+
/** Project-specific instruction sections wrapped around browser authentication guidance. */
|
|
18
|
+
export interface PypiTrustedPublisherInstructions {
|
|
19
|
+
readonly afterAuthentication: readonly string[];
|
|
20
|
+
readonly beforeAuthentication: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Parse task options and write the complete trusted-publisher instructions. */
|
|
24
|
+
export async function printPypiTrustedPublisherInstructions(
|
|
25
|
+
instructions: PypiTrustedPublisherInstructions,
|
|
26
|
+
argv: readonly string[] = process.argv.slice(2),
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const { secretFile } = await taskOptions(
|
|
29
|
+
taskCommand(
|
|
30
|
+
import.meta.url,
|
|
31
|
+
"Print system-browser instructions for PyPI trusted publishers",
|
|
32
|
+
PypiTrustedPublisherInstructionsOptionsSchema,
|
|
33
|
+
),
|
|
34
|
+
PypiTrustedPublisherInstructionsOptionsSchema,
|
|
35
|
+
argv,
|
|
36
|
+
);
|
|
37
|
+
const authentication = secretFile
|
|
38
|
+
? [
|
|
39
|
+
`- If browser authentication is required, read credentials from ${secretFile} directly into the browser without printing, logging, or exposing secret values.`,
|
|
40
|
+
`- If ${secretFile} is absent, invalid, or insufficient for authentication, pause and ask the user to complete authentication.`,
|
|
41
|
+
]
|
|
42
|
+
: [
|
|
43
|
+
"- If browser authentication is required, pause and ask the user to complete authentication.",
|
|
44
|
+
];
|
|
45
|
+
process.stdout.write(
|
|
46
|
+
`${[
|
|
47
|
+
...instructions.beforeAuthentication,
|
|
48
|
+
...authentication,
|
|
49
|
+
...instructions.afterAuthentication,
|
|
50
|
+
]
|
|
51
|
+
.join("\n")
|
|
52
|
+
.trimEnd()}\n`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -13,7 +13,7 @@ import ts from "typescript";
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
|
|
15
15
|
import { TaskCheckOptionSchema, TaskForceOptionSchema, TaskRootOptionSchema } from "./options.ts";
|
|
16
|
-
import { header, makeReadonly, makeWritable } from "../src/generated.ts";
|
|
16
|
+
import { header, headerLines, makeReadonly, makeWritable } from "../src/generated.ts";
|
|
17
17
|
import { publicFunctionExports } from "../src/module-exports.ts";
|
|
18
18
|
import { resolveRepoRoot } from "../src/packages.ts";
|
|
19
19
|
import {
|
|
@@ -434,9 +434,11 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
434
434
|
section.includes("Callable["),
|
|
435
435
|
);
|
|
436
436
|
return [
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
437
|
+
...headerLines({
|
|
438
|
+
comment: "#",
|
|
439
|
+
tool: "projen/tasks/python-node-bindings.ts",
|
|
440
|
+
source,
|
|
441
|
+
}),
|
|
440
442
|
"",
|
|
441
443
|
"from __future__ import annotations",
|
|
442
444
|
"",
|
|
@@ -1162,9 +1164,11 @@ function removeEmptyDirectories(directory: string): void {
|
|
|
1162
1164
|
|
|
1163
1165
|
function pythonRuntimeLoader(source: string): string {
|
|
1164
1166
|
return [
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1167
|
+
...headerLines({
|
|
1168
|
+
comment: "#",
|
|
1169
|
+
tool: "projen/tasks/python-node-bindings.ts",
|
|
1170
|
+
source,
|
|
1171
|
+
}),
|
|
1168
1172
|
"",
|
|
1169
1173
|
"from __future__ import annotations",
|
|
1170
1174
|
"",
|