@hypequery/cli 1.13.1 → 1.14.0
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/deployment.d.ts +1 -0
- package/dist/commands/deployment.d.ts.map +1 -1
- package/dist/commands/deployment.js +9 -1
- package/dist/utils/deployment-bundle.d.ts +14 -1
- package/dist/utils/deployment-bundle.d.ts.map +1 -1
- package/dist/utils/deployment-bundle.js +58 -1
- package/dist/utils/deployment-source-snapshot.d.ts +3 -0
- package/dist/utils/deployment-source-snapshot.d.ts.map +1 -0
- package/dist/utils/deployment-source-snapshot.js +110 -0
- package/dist/utils/deployment-upload.d.ts.map +1 -1
- package/dist/utils/deployment-upload.js +16 -0
- package/package.json +4 -4
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA8ND,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -142,6 +142,7 @@ program
|
|
|
142
142
|
.option('--runtime-output <path>', 'Bundled Node runtime path (default: beside deployment JSON)')
|
|
143
143
|
.option('--entrypoint-prefix <prefix>', 'Runtime entrypoint prefix (default: queries)')
|
|
144
144
|
.option('--hash-output <path>', 'Deployment identity sidecar path (default: <output>.sha256)')
|
|
145
|
+
.option('--no-source', 'Exclude project source files from the deployment bundle')
|
|
145
146
|
.action(runCommand(async (api, options) => {
|
|
146
147
|
await buildDeploymentCommand(api, options);
|
|
147
148
|
}));
|
|
@@ -176,6 +177,7 @@ program
|
|
|
176
177
|
.option('--project <project>', 'Target project identifier (advanced override)')
|
|
177
178
|
.option('--environment <environment>', 'Target environment identifier (advanced override)')
|
|
178
179
|
.option('--release <path>', 'Submit a prebuilt bundle with this release (legacy)')
|
|
180
|
+
.option('--no-source', 'Exclude project source files from the deployment bundle')
|
|
179
181
|
.option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
|
|
180
182
|
.action(runCommand(async (source, options) => {
|
|
181
183
|
await deployCommand(source, options);
|
|
@@ -10,6 +10,7 @@ export interface DeployOptions extends SubmitDeploymentOptions {
|
|
|
10
10
|
environment?: string;
|
|
11
11
|
bundleOutput?: string;
|
|
12
12
|
releaseOutput?: string;
|
|
13
|
+
source?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export interface SubmitDeploymentDependencies {
|
|
15
16
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,uBAAuB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA8GD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAwDvC;AAmDD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAmDvC"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -204,7 +204,7 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
|
|
|
204
204
|
// a missing or expired login only surfaces after a full bundle build.
|
|
205
205
|
await resolveDeploymentCredential(options.endpoint, dependencies);
|
|
206
206
|
try {
|
|
207
|
-
await build(sourcePath, { bundleOutput: bundlePath });
|
|
207
|
+
await build(sourcePath, { bundleOutput: bundlePath, source: options.source });
|
|
208
208
|
}
|
|
209
209
|
catch (error) {
|
|
210
210
|
throw withRuntimeFlagHint(error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EACvC,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAI5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAsJrC;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,0BAA0B,CAAC,CA2ErC;AAiED,wBAAsB,+BAA+B,CACnD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,+BAAoC,EAC7C,YAAY,GAAE,oCAAyC,GACtD,OAAO,CAAC,iCAAiC,CAAC,CAkE5C"}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { prepareProtocolDeploymentReleaseEnvelope, prepareProtocolDeploymentContract, } from '@hypequery/protocol';
|
|
5
5
|
import { writeDeploymentBundle, verifyDeploymentBundle, readDeploymentRuntimeFile, } from '../utils/deployment-bundle.js';
|
|
6
6
|
import { buildNodeRuntimeArtifact, getDeploymentRuntimeEntrypoints, } from '../utils/deployment-runtime-artifact.js';
|
|
7
|
+
import { captureDeploymentSourceSnapshot } from '../utils/deployment-source-snapshot.js';
|
|
7
8
|
import { loadApiModule } from '../utils/load-api.js';
|
|
8
9
|
import { logger } from '../utils/logger.js';
|
|
9
10
|
import { loadCloudCredential, } from '../utils/cloud-credential-store.js';
|
|
@@ -123,8 +124,15 @@ export async function buildDeploymentCommand(apiPath, options = {}) {
|
|
|
123
124
|
}
|
|
124
125
|
runtimeFiles.push({ runtime, sha256: actualSha256, bytes });
|
|
125
126
|
}
|
|
126
|
-
const
|
|
127
|
+
const sourceSnapshot = options.source === false
|
|
128
|
+
? undefined
|
|
129
|
+
: await captureDeploymentSourceSnapshot(apiPath);
|
|
130
|
+
const bundle = await writeDeploymentBundle(bundleOutput, prepared, runtimeFiles, sourceSnapshot);
|
|
127
131
|
logger.success(`Deployment bundle written to ${bundle.directory}`);
|
|
132
|
+
if (bundle.manifest.source) {
|
|
133
|
+
logger.info(`Captured ${bundle.manifest.source.files.length} source `
|
|
134
|
+
+ `${bundle.manifest.source.files.length === 1 ? 'file' : 'files'}`);
|
|
135
|
+
}
|
|
128
136
|
logger.info(`Bundle identity: ${bundle.identity}`);
|
|
129
137
|
logger.info(`Deployment identity: ${digest}`);
|
|
130
138
|
return validated;
|
|
@@ -8,12 +8,25 @@ export interface DeploymentBundleRuntimeFile {
|
|
|
8
8
|
readonly sha256: string;
|
|
9
9
|
readonly bytes: Uint8Array;
|
|
10
10
|
}
|
|
11
|
+
export interface DeploymentBundleSourceFile {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly bytes: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
export interface DeploymentBundleSourceSnapshot {
|
|
16
|
+
readonly entrypoint: string;
|
|
17
|
+
readonly files: readonly DeploymentBundleSourceFile[];
|
|
18
|
+
readonly revision?: {
|
|
19
|
+
readonly kind: 'git';
|
|
20
|
+
readonly commit: string;
|
|
21
|
+
readonly dirty: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
11
24
|
export interface WrittenDeploymentBundle {
|
|
12
25
|
readonly directory: string;
|
|
13
26
|
readonly manifest: ProtocolDeploymentBundleManifest;
|
|
14
27
|
readonly identity: string;
|
|
15
28
|
readonly contract: ProtocolDeploymentContract;
|
|
16
29
|
}
|
|
17
|
-
export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, runtimeFiles: readonly DeploymentBundleRuntimeFile[]): Promise<WrittenDeploymentBundle>;
|
|
30
|
+
export declare function writeDeploymentBundle(outputDirectory: string, prepared: PreparedProtocolDeploymentContract, runtimeFiles: readonly DeploymentBundleRuntimeFile[], sourceSnapshot?: DeploymentBundleSourceSnapshot): Promise<WrittenDeploymentBundle>;
|
|
18
31
|
export declare function readDeploymentRuntimeFile(filePath: string): Promise<Uint8Array>;
|
|
19
32
|
//# sourceMappingURL=deployment-bundle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment-bundle.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,CAAC;AACF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC/C;
|
|
1
|
+
{"version":3,"file":"deployment-bundle.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAGtE,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,CAAC;AACF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC/C;AA0ID,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,kCAAkC,EAC5C,YAAY,EAAE,SAAS,2BAA2B,EAAE,EACpD,cAAc,CAAC,EAAE,8BAA8B,GAC9C,OAAO,CAAC,uBAAuB,CAAC,CA+FlC;AAoCD,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAMrF"}
|
|
@@ -12,6 +12,7 @@ function sha256(bytes) {
|
|
|
12
12
|
function runtimeArtifactPath(runtime, digest) {
|
|
13
13
|
return `artifacts/${digest}.${runtime === 'node' ? 'mjs' : 'pyz'}`;
|
|
14
14
|
}
|
|
15
|
+
const SOURCE_ROOT = 'source';
|
|
15
16
|
function errorCode(error) {
|
|
16
17
|
return typeof error === 'object' && error !== null && 'code' in error
|
|
17
18
|
? String(error.code)
|
|
@@ -83,12 +84,51 @@ function requireClosedContractArtifactSet(contract) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
function validateSourceSnapshot(source) {
|
|
88
|
+
if (!source)
|
|
89
|
+
return undefined;
|
|
90
|
+
const files = [...source.files].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
91
|
+
if (files.length < 1
|
|
92
|
+
|| files.length > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFiles) {
|
|
93
|
+
throw new Error('Deployment source snapshot exceeds the bundle file limits.');
|
|
94
|
+
}
|
|
95
|
+
let totalBytes = 0;
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const portable = file.path.split(path.sep).join('/');
|
|
99
|
+
if (portable !== file.path || path.isAbsolute(file.path)
|
|
100
|
+
|| file.path.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
|
|
101
|
+
throw new Error(`Deployment source path must be project-relative: ${file.path}`);
|
|
102
|
+
}
|
|
103
|
+
const caseFolded = file.path.toLowerCase();
|
|
104
|
+
if (seen.has(caseFolded)) {
|
|
105
|
+
throw new Error(`Duplicate deployment source path: ${file.path}`);
|
|
106
|
+
}
|
|
107
|
+
seen.add(caseFolded);
|
|
108
|
+
if (file.bytes.byteLength > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFileBytes) {
|
|
109
|
+
throw new Error(`Deployment source file exceeds its byte limit: ${file.path}`);
|
|
110
|
+
}
|
|
111
|
+
totalBytes += file.bytes.byteLength;
|
|
112
|
+
}
|
|
113
|
+
if (totalBytes > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceBytes) {
|
|
114
|
+
throw new Error('Deployment source snapshot exceeds its total byte limit.');
|
|
115
|
+
}
|
|
116
|
+
if (!files.some(file => file.path === source.entrypoint)) {
|
|
117
|
+
throw new Error('Deployment source snapshot does not contain its API entrypoint.');
|
|
118
|
+
}
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
entrypoint: source.entrypoint,
|
|
121
|
+
files: Object.freeze(files),
|
|
122
|
+
...(source.revision ? { revision: Object.freeze({ ...source.revision }) } : {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFiles, sourceSnapshot) {
|
|
87
126
|
const destination = path.resolve(outputDirectory);
|
|
88
127
|
if (destination === path.parse(destination).root) {
|
|
89
128
|
throw new Error('The deployment bundle output cannot be a filesystem root.');
|
|
90
129
|
}
|
|
91
130
|
const files = validateRuntimeFiles(prepared, runtimeFiles);
|
|
131
|
+
const source = validateSourceSnapshot(sourceSnapshot);
|
|
92
132
|
const deploymentBytes = utf8Encoder.encode(`${prepared.canonical}\n`);
|
|
93
133
|
const manifestInput = {
|
|
94
134
|
kind: 'hypequery-deployment-bundle',
|
|
@@ -105,6 +145,18 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
|
|
|
105
145
|
sha256: file.sha256,
|
|
106
146
|
byteLength: file.bytes.byteLength,
|
|
107
147
|
})),
|
|
148
|
+
...(source ? {
|
|
149
|
+
source: {
|
|
150
|
+
root: SOURCE_ROOT,
|
|
151
|
+
entrypoint: source.entrypoint,
|
|
152
|
+
files: source.files.map(file => ({
|
|
153
|
+
path: file.path,
|
|
154
|
+
sha256: sha256(file.bytes),
|
|
155
|
+
byteLength: file.bytes.byteLength,
|
|
156
|
+
})),
|
|
157
|
+
...(source.revision ? { revision: source.revision } : {}),
|
|
158
|
+
},
|
|
159
|
+
} : {}),
|
|
108
160
|
};
|
|
109
161
|
const preparedManifest = prepareProtocolDeploymentBundleManifest(manifestInput);
|
|
110
162
|
const replaceExisting = await existingVerifiedBundle(destination);
|
|
@@ -118,6 +170,11 @@ export async function writeDeploymentBundle(outputDirectory, prepared, runtimeFi
|
|
|
118
170
|
for (const file of files) {
|
|
119
171
|
await writeFile(path.join(staging, runtimeArtifactPath(file.runtime, file.sha256)), file.bytes, { flag: 'wx' });
|
|
120
172
|
}
|
|
173
|
+
for (const file of source?.files ?? []) {
|
|
174
|
+
const outputPath = path.join(staging, SOURCE_ROOT, ...file.path.split('/'));
|
|
175
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
176
|
+
await writeFile(outputPath, file.bytes, { flag: 'wx' });
|
|
177
|
+
}
|
|
121
178
|
await writeFile(path.join(staging, DEPLOYMENT_BUNDLE_MANIFEST), `${preparedManifest.canonical}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
122
179
|
if (!replaceExisting) {
|
|
123
180
|
await rename(staging, destination);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-source-snapshot.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-source-snapshot.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,8BAA8B,EAC/B,MAAM,wBAAwB,CAAC;AAqEhC,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,8BAA8B,CAAC,CAkDzC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { constants, lstat, open, realpath } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS, } from '@hypequery/protocol';
|
|
5
|
+
import { build } from 'esbuild';
|
|
6
|
+
import { findNearestTsconfig } from './load-api.js';
|
|
7
|
+
function portableProjectPath(projectRoot, absolutePath) {
|
|
8
|
+
const relative = path.relative(projectRoot, absolutePath);
|
|
9
|
+
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`)
|
|
10
|
+
|| path.isAbsolute(relative)) {
|
|
11
|
+
throw new Error('Deployment source files must remain inside the current project.');
|
|
12
|
+
}
|
|
13
|
+
return relative.split(path.sep).join('/');
|
|
14
|
+
}
|
|
15
|
+
async function readSourceFile(projectRoot, inputPath) {
|
|
16
|
+
const absolutePath = path.resolve(projectRoot, inputPath);
|
|
17
|
+
const displayPath = portableProjectPath(projectRoot, absolutePath);
|
|
18
|
+
const resolvedPath = await realpath(absolutePath);
|
|
19
|
+
if (resolvedPath !== absolutePath) {
|
|
20
|
+
throw new Error(`Deployment source files must not be symbolic links: ${displayPath}`);
|
|
21
|
+
}
|
|
22
|
+
const initial = await lstat(absolutePath);
|
|
23
|
+
if (!initial.isFile() || initial.isSymbolicLink()) {
|
|
24
|
+
throw new Error(`Deployment source entry is not a regular file: ${displayPath}`);
|
|
25
|
+
}
|
|
26
|
+
if (initial.size > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFileBytes) {
|
|
27
|
+
throw new Error(`Deployment source file exceeds its byte limit: ${displayPath}`);
|
|
28
|
+
}
|
|
29
|
+
const handle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
30
|
+
try {
|
|
31
|
+
const stat = await handle.stat();
|
|
32
|
+
if (!stat.isFile() || stat.size !== initial.size) {
|
|
33
|
+
throw new Error(`Deployment source file changed while being read: ${displayPath}`);
|
|
34
|
+
}
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
path: displayPath,
|
|
37
|
+
bytes: new Uint8Array(await handle.readFile()),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await handle.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function gitOutput(projectRoot, arguments_) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
execFile('git', [...arguments_], { cwd: projectRoot, encoding: 'utf8', maxBuffer: 1024 * 1024 }, (error, stdout) => resolve(error ? undefined : stdout.trim()));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function sourceRevision(projectRoot) {
|
|
50
|
+
const commit = await gitOutput(projectRoot, ['rev-parse', 'HEAD']);
|
|
51
|
+
if (!commit || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
|
|
52
|
+
return undefined;
|
|
53
|
+
const status = await gitOutput(projectRoot, ['status', '--porcelain', '--untracked-files=all']);
|
|
54
|
+
if (status === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
kind: 'git',
|
|
58
|
+
commit,
|
|
59
|
+
dirty: status.length > 0,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function captureDeploymentSourceSnapshot(apiPath) {
|
|
63
|
+
const projectRoot = await realpath(process.cwd());
|
|
64
|
+
const entrypointAbsolute = await realpath(path.resolve(projectRoot, apiPath));
|
|
65
|
+
const entrypoint = portableProjectPath(projectRoot, entrypointAbsolute);
|
|
66
|
+
let inputs;
|
|
67
|
+
try {
|
|
68
|
+
const result = await build({
|
|
69
|
+
absWorkingDir: projectRoot,
|
|
70
|
+
bundle: true,
|
|
71
|
+
entryPoints: [entrypointAbsolute],
|
|
72
|
+
logLevel: 'silent',
|
|
73
|
+
metafile: true,
|
|
74
|
+
packages: 'external',
|
|
75
|
+
platform: 'node',
|
|
76
|
+
sourcemap: false,
|
|
77
|
+
tsconfig: await findNearestTsconfig(entrypointAbsolute) ?? undefined,
|
|
78
|
+
write: false,
|
|
79
|
+
});
|
|
80
|
+
inputs = Object.keys(result.metafile.inputs);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Error(`Could not resolve the deployment source graph from ${apiPath}.\n`
|
|
84
|
+
+ `Original error: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
}
|
|
86
|
+
const paths = [...new Set(inputs.map(input => {
|
|
87
|
+
const absolute = path.resolve(projectRoot, input);
|
|
88
|
+
const relative = portableProjectPath(projectRoot, absolute);
|
|
89
|
+
if (relative.split('/').includes('node_modules')) {
|
|
90
|
+
throw new Error(`Deployment source graph unexpectedly included a dependency: ${relative}`);
|
|
91
|
+
}
|
|
92
|
+
return relative;
|
|
93
|
+
}))].sort();
|
|
94
|
+
if (!paths.includes(entrypoint))
|
|
95
|
+
paths.unshift(entrypoint);
|
|
96
|
+
if (paths.length > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceFiles) {
|
|
97
|
+
throw new Error('Deployment source graph exceeds the bundle file limit.');
|
|
98
|
+
}
|
|
99
|
+
const files = await Promise.all(paths.map(file => readSourceFile(projectRoot, file)));
|
|
100
|
+
const totalBytes = files.reduce((total, file) => total + file.bytes.byteLength, 0);
|
|
101
|
+
if (totalBytes > DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceBytes) {
|
|
102
|
+
throw new Error('Deployment source graph exceeds the bundle byte limit.');
|
|
103
|
+
}
|
|
104
|
+
const revision = await sourceRevision(projectRoot);
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
entrypoint,
|
|
107
|
+
files: Object.freeze(files),
|
|
108
|
+
...(revision ? { revision } : {}),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAE1E,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAQ1E,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AA+
|
|
1
|
+
{"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAE1E,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAQ1E,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AA+WD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CAsF3B"}
|
|
@@ -78,6 +78,9 @@ function validateMultipartBundlePaths(bundle) {
|
|
|
78
78
|
multipartBundlePath(bundle.manifest.deployment.path);
|
|
79
79
|
for (const artifact of bundle.manifest.artifacts)
|
|
80
80
|
multipartBundlePath(artifact.path);
|
|
81
|
+
for (const source of bundle.manifest.source?.files ?? []) {
|
|
82
|
+
multipartBundlePath(`${bundle.manifest.source.root}/${source.path}`);
|
|
83
|
+
}
|
|
81
84
|
}
|
|
82
85
|
function partHeader(boundary, part) {
|
|
83
86
|
const headers = [
|
|
@@ -130,6 +133,19 @@ function uploadParts(bundle, release, manifest) {
|
|
|
130
133
|
byteLength: artifact.byteLength,
|
|
131
134
|
sha256: artifact.sha256,
|
|
132
135
|
})),
|
|
136
|
+
...(manifest.manifest.source?.files.map(source => {
|
|
137
|
+
const bundlePath = `${manifest.manifest.source.root}/${source.path}`;
|
|
138
|
+
return {
|
|
139
|
+
kind: 'file',
|
|
140
|
+
name: 'bundle',
|
|
141
|
+
filename: path.basename(source.path),
|
|
142
|
+
contentType: 'application/octet-stream',
|
|
143
|
+
absolutePath: path.join(root, ...bundlePath.split('/')),
|
|
144
|
+
bundlePath,
|
|
145
|
+
byteLength: source.byteLength,
|
|
146
|
+
sha256: source.sha256,
|
|
147
|
+
};
|
|
148
|
+
}) ?? []),
|
|
133
149
|
]);
|
|
134
150
|
}
|
|
135
151
|
async function* streamVerifiedFile(part) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Command-line interface for hypequery",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"open": "^10.0.0",
|
|
20
20
|
"ora": "^8.0.1",
|
|
21
21
|
"prompts": "^2.4.2",
|
|
22
|
-
"@hypequery/
|
|
23
|
-
"@hypequery/
|
|
22
|
+
"@hypequery/deployment": "0.7.0",
|
|
23
|
+
"@hypequery/protocol": "0.10.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
26
|
"@napi-rs/keyring": "1.3.0"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@vitest/coverage-v8": "^3.2.6",
|
|
35
35
|
"typescript": "^5.7.3",
|
|
36
36
|
"vitest": "^3.2.6",
|
|
37
|
-
"@hypequery/serve": "0.14.
|
|
37
|
+
"@hypequery/serve": "0.14.1"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|