@mondaydotcomorg/z2h-cli 0.33.3 → 0.33.4
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/commands/backend.d.ts +0 -1
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +7 -30
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +2 -0
- package/dist/esm/commands/backend.d.ts +0 -1
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +3 -25
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +2 -0
- package/dist/esm/util/__tests__/usage-json.test.d.ts +2 -0
- package/dist/esm/util/__tests__/usage-json.test.d.ts.map +1 -0
- package/dist/esm/util/git/repo.d.ts +6 -0
- package/dist/esm/util/git/repo.d.ts.map +1 -1
- package/dist/esm/util/git/repo.mjs +1 -1
- package/dist/esm/util/usage-json.d.ts +6 -0
- package/dist/esm/util/usage-json.d.ts.map +1 -0
- package/dist/esm/util/usage-json.mjs +36 -0
- package/dist/util/__tests__/usage-json.test.d.ts +2 -0
- package/dist/util/__tests__/usage-json.test.d.ts.map +1 -0
- package/dist/util/git/repo.d.ts +6 -0
- package/dist/util/git/repo.d.ts.map +1 -1
- package/dist/util/git/repo.js +1 -0
- package/dist/util/usage-json.d.ts +6 -0
- package/dist/util/usage-json.d.ts.map +1 -0
- package/dist/util/usage-json.js +44 -0
- package/package.json +1 -1
- package/src/commands/__tests__/backend-deploy.test.ts +5 -64
- package/src/commands/__tests__/deploy.test.ts +17 -0
- package/src/commands/backend.ts +2 -26
- package/src/commands/deploy.ts +2 -0
- package/src/util/__tests__/usage-json.test.ts +58 -0
- package/src/util/git/repo.ts +1 -1
- package/src/util/usage-json.ts +35 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AA+BD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CASrC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F;AAsBD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,EAC5C,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/commands/backend.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
4
|
const promises = require('node:fs/promises');
|
|
6
|
-
const fs
|
|
5
|
+
const fs = require('fs-extra');
|
|
7
6
|
const clientS3 = require('@aws-sdk/client-s3');
|
|
8
7
|
const util_logger = require('../util/logger.js');
|
|
9
8
|
const util_appName = require('../util/app-name.js');
|
|
@@ -16,6 +15,7 @@ const backend_client = require('../backend/client.js');
|
|
|
16
15
|
const backend_formatSandboxRun = require('../backend/format-sandbox-run.js');
|
|
17
16
|
const backend_validate = require('../backend/validate.js');
|
|
18
17
|
const util_errors = require('../util/errors.js');
|
|
18
|
+
const util_usageJson = require('../util/usage-json.js');
|
|
19
19
|
|
|
20
20
|
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
21
21
|
|
|
@@ -28,36 +28,14 @@ function currentAppName() {
|
|
|
28
28
|
}
|
|
29
29
|
return util_appName.mfAppName(raw);
|
|
30
30
|
}
|
|
31
|
-
function hasHandlers(appDir) {
|
|
32
|
-
return fs.existsSync(path__default.default.join(appDir, 'backend', 'handlers'));
|
|
33
|
-
}
|
|
34
|
-
/** Other features may add top-level fields, so writes below merge into this copy instead of replacing it. */
|
|
35
|
-
async function readExistingUsage(appDir) {
|
|
36
|
-
try {
|
|
37
|
-
return (await fs$1.readJson(path__default.default.join(appDir, 'usage.json')));
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
if (err.code === 'ENOENT') {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
throw err;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/** Keeps the app's git-tracked `usage.json` current with every handler parse (validate or deploy). */
|
|
47
|
-
async function writeUsageJson(appDir, patch) {
|
|
48
|
-
const existing = await readExistingUsage(appDir);
|
|
49
|
-
await fs$1.writeJson(path__default.default.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
|
|
50
|
-
}
|
|
51
31
|
async function readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel) {
|
|
52
|
-
if (!hasHandlers(appDir)) {
|
|
53
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages: {} });
|
|
32
|
+
if (!util_usageJson.hasHandlers(appDir)) {
|
|
54
33
|
return { integrations: [], handlerNames: [], apiUsages: {} };
|
|
55
34
|
}
|
|
56
35
|
util_logger.info(`reading backend handlers ${logLabel}`);
|
|
57
36
|
const { handlers, apiDomains, apiUsages } = await backend_handlers.readHandlers(appDir);
|
|
58
37
|
const integrations = backend_detectIntegrations.detectIntegrations(apiDomains);
|
|
59
38
|
const handlerNames = Object.keys(handlers);
|
|
60
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
61
39
|
if (Object.keys(handlers).length === 0) {
|
|
62
40
|
return { integrations, handlerNames, apiUsages };
|
|
63
41
|
}
|
|
@@ -93,15 +71,15 @@ async function deployBackend(opts) {
|
|
|
93
71
|
* local `usage.json` — no upload to S3. Throws on the first invalid handler.
|
|
94
72
|
*/
|
|
95
73
|
async function backendValidateCommand(appDir = process.cwd()) {
|
|
96
|
-
if (!hasHandlers(appDir)) {
|
|
74
|
+
if (!util_usageJson.hasHandlers(appDir)) {
|
|
97
75
|
util_logger.output('[z2h-cli] no backend/handlers/ directory — nothing to validate', { handlers: [], integrations: [] });
|
|
98
76
|
return;
|
|
99
77
|
}
|
|
100
78
|
const { handlers, apiDomains, apiUsages } = await backend_handlers.readHandlers(appDir);
|
|
101
79
|
const names = Object.keys(handlers).sort();
|
|
102
80
|
const integrations = backend_detectIntegrations.detectIntegrations(apiDomains);
|
|
103
|
-
const { name } = (await fs
|
|
104
|
-
await writeUsageJson(appDir, { appName: name ? util_appName.mfAppName(name) : undefined, deployedBy: util_machineIdentity.whoAmI(), apiUsages });
|
|
81
|
+
const { name } = (await fs.readJson(path__default.default.join(appDir, 'package.json')));
|
|
82
|
+
await util_usageJson.writeUsageJson(appDir, { appName: name ? util_appName.mfAppName(name) : undefined, deployedBy: util_machineIdentity.whoAmI(), apiUsages });
|
|
105
83
|
const list = (xs) => (xs.length ? xs.join(', ') : 'none');
|
|
106
84
|
util_logger.output(`[z2h-cli] ${names.length} handler(s) valid: ${list(names)}\n[z2h-cli] integrations: ${list(integrations)}`, {
|
|
107
85
|
handlers: names,
|
|
@@ -117,7 +95,7 @@ async function readHandlerSource(appDir, handlerName) {
|
|
|
117
95
|
if (err.code !== 'ENOENT') {
|
|
118
96
|
throw err;
|
|
119
97
|
}
|
|
120
|
-
const files = hasHandlers(appDir) ? await promises.readdir(dir) : [];
|
|
98
|
+
const files = util_usageJson.hasHandlers(appDir) ? await promises.readdir(dir) : [];
|
|
121
99
|
const available = files
|
|
122
100
|
.filter(f => f.endsWith('.js'))
|
|
123
101
|
.map(f => path__default.default.parse(f).name)
|
|
@@ -155,4 +133,3 @@ async function backendInvokeCommand(handlerName, opts = {}, appDir = process.cwd
|
|
|
155
133
|
exports.backendInvokeCommand = backendInvokeCommand;
|
|
156
134
|
exports.backendValidateCommand = backendValidateCommand;
|
|
157
135
|
exports.deployBackend = deployBackend;
|
|
158
|
-
exports.hasHandlers = hasHandlers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAatG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -11,6 +11,7 @@ const util_auth_brokerCredentialProvider = require('../util/auth/broker-credenti
|
|
|
11
11
|
const util_auth_authStore = require('../util/auth/auth-store.js');
|
|
12
12
|
const util_appName = require('../util/app-name.js');
|
|
13
13
|
const util_git_repo = require('../util/git/repo.js');
|
|
14
|
+
const util_usageJson = require('../util/usage-json.js');
|
|
14
15
|
const util_git_gitEnv = require('../util/git/git-env.js');
|
|
15
16
|
const util_git_remoteOptions = require('../util/git/remote-options.js');
|
|
16
17
|
const util_s3_client = require('../util/s3/client.js');
|
|
@@ -216,6 +217,7 @@ async function deployLive({ paths, consumerName, appName, token, opts }) {
|
|
|
216
217
|
await util_git_repo.guardCleanTree(process.cwd());
|
|
217
218
|
util_logger.info('syncing with git remote');
|
|
218
219
|
await util_git_repo.pullAndResolve(process.cwd(), gitEnv);
|
|
220
|
+
await util_usageJson.updateOrCreateUsageJson(process.cwd(), appName, util_machineIdentity.whoAmI());
|
|
219
221
|
}
|
|
220
222
|
catch (err) {
|
|
221
223
|
throw util_errors.tagDeployError('git_error', err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/commands/backend.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AA+BD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CASrC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,GAAE,MAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F;AAsBD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,EAC5C,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
2
|
import { readFile, readdir } from 'node:fs/promises';
|
|
4
|
-
import { readJson
|
|
3
|
+
import { readJson } from 'fs-extra';
|
|
5
4
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
6
5
|
import { output, info } from '../util/logger.mjs';
|
|
7
6
|
import { mfAppName, getCwdAppName } from '../util/app-name.mjs';
|
|
@@ -14,6 +13,7 @@ import { runSandbox } from '../backend/client.mjs';
|
|
|
14
13
|
import { formatSandboxRun } from '../backend/format-sandbox-run.mjs';
|
|
15
14
|
import { validateHandlerSource } from '../backend/validate.mjs';
|
|
16
15
|
import { tagDeployError } from '../util/errors.mjs';
|
|
16
|
+
import { hasHandlers, writeUsageJson } from '../util/usage-json.mjs';
|
|
17
17
|
|
|
18
18
|
function currentAppName() {
|
|
19
19
|
const raw = getCwdAppName();
|
|
@@ -22,36 +22,14 @@ function currentAppName() {
|
|
|
22
22
|
}
|
|
23
23
|
return mfAppName(raw);
|
|
24
24
|
}
|
|
25
|
-
function hasHandlers(appDir) {
|
|
26
|
-
return existsSync(path.join(appDir, 'backend', 'handlers'));
|
|
27
|
-
}
|
|
28
|
-
/** Other features may add top-level fields, so writes below merge into this copy instead of replacing it. */
|
|
29
|
-
async function readExistingUsage(appDir) {
|
|
30
|
-
try {
|
|
31
|
-
return (await readJson(path.join(appDir, 'usage.json')));
|
|
32
|
-
}
|
|
33
|
-
catch (err) {
|
|
34
|
-
if (err.code === 'ENOENT') {
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/** Keeps the app's git-tracked `usage.json` current with every handler parse (validate or deploy). */
|
|
41
|
-
async function writeUsageJson(appDir, patch) {
|
|
42
|
-
const existing = await readExistingUsage(appDir);
|
|
43
|
-
await writeJson(path.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
|
|
44
|
-
}
|
|
45
25
|
async function readDetectAndUpload(appDir, prefix, appName, version, deployedBy, logLabel) {
|
|
46
26
|
if (!hasHandlers(appDir)) {
|
|
47
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages: {} });
|
|
48
27
|
return { integrations: [], handlerNames: [], apiUsages: {} };
|
|
49
28
|
}
|
|
50
29
|
info(`reading backend handlers ${logLabel}`);
|
|
51
30
|
const { handlers, apiDomains, apiUsages } = await readHandlers(appDir);
|
|
52
31
|
const integrations = detectIntegrations(apiDomains);
|
|
53
32
|
const handlerNames = Object.keys(handlers);
|
|
54
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
55
33
|
if (Object.keys(handlers).length === 0) {
|
|
56
34
|
return { integrations, handlerNames, apiUsages };
|
|
57
35
|
}
|
|
@@ -146,4 +124,4 @@ async function backendInvokeCommand(handlerName, opts = {}, appDir = process.cwd
|
|
|
146
124
|
}
|
|
147
125
|
}
|
|
148
126
|
|
|
149
|
-
export { backendInvokeCommand, backendValidateCommand, deployBackend
|
|
127
|
+
export { backendInvokeCommand, backendValidateCommand, deployBackend };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAatG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
|
|
@@ -9,6 +9,7 @@ import { fetchDeployVersion, brokerCredentialProvider } from '../util/auth/broke
|
|
|
9
9
|
import { readZthAuth } from '../util/auth/auth-store.mjs';
|
|
10
10
|
import { validateAppName, cleanAppName, mfAppName, buildPreviewSlug } from '../util/app-name.mjs';
|
|
11
11
|
import { getRemoteUrl, initRepo, guardCleanTree, pullAndResolve, pushToRemote } from '../util/git/repo.mjs';
|
|
12
|
+
import { updateOrCreateUsageJson } from '../util/usage-json.mjs';
|
|
12
13
|
import { resolveGitEnv } from '../util/git/git-env.mjs';
|
|
13
14
|
import { validateRemoteOptions, resolveRemoteUrl } from '../util/git/remote-options.mjs';
|
|
14
15
|
import { setupS3Client } from '../util/s3/client.mjs';
|
|
@@ -210,6 +211,7 @@ async function deployLive({ paths, consumerName, appName, token, opts }) {
|
|
|
210
211
|
await guardCleanTree(process.cwd());
|
|
211
212
|
info('syncing with git remote');
|
|
212
213
|
await pullAndResolve(process.cwd(), gitEnv);
|
|
214
|
+
await updateOrCreateUsageJson(process.cwd(), appName, whoAmI());
|
|
213
215
|
}
|
|
214
216
|
catch (err) {
|
|
215
217
|
throw tagDeployError('git_error', err);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-json.test.d.ts","sourceRoot":"","sources":["../../../../src/util/__tests__/usage-json.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
type GitEnv = Record<string, string | undefined>;
|
|
2
|
+
export declare function execGit(args: string[], dir: string, env?: GitEnv): import("execa").ResultPromise<{
|
|
3
|
+
cwd: string;
|
|
4
|
+
env: GitEnv | undefined;
|
|
5
|
+
}>;
|
|
1
6
|
export interface ConflictInfo {
|
|
2
7
|
localCommits: string[];
|
|
3
8
|
remoteCommits: string[];
|
|
@@ -25,4 +30,5 @@ export declare function pullAndResolve(dir: string, env?: Record<string, string>
|
|
|
25
30
|
export declare function pushToRemote(dir: string, env?: Record<string, string>): Promise<void>;
|
|
26
31
|
export declare function remoteHead(dir: string): Promise<string>;
|
|
27
32
|
export declare function localHead(dir: string): Promise<string>;
|
|
33
|
+
export {};
|
|
28
34
|
//# sourceMappingURL=repo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../../src/util/git/repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../../src/util/git/repo.ts"],"names":[],"mappings":"AAOA,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;;;GAEhE;AAQD,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAKD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI3E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5G;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3F;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5D"}
|
|
@@ -171,4 +171,4 @@ async function ensureGitignore(dir) {
|
|
|
171
171
|
await fs.outputFile(gitignorePath, content + separator + missing.join('\n') + '\n');
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
export { cloneRepo, defaultS3Remote, getRemoteUrl, guardCleanTree, initRepo, localHead, pullAndResolve, pushToRemote, remoteHead, safeBackupIfDirty };
|
|
174
|
+
export { cloneRepo, defaultS3Remote, execGit, getRemoteUrl, guardCleanTree, initRepo, localHead, pullAndResolve, pushToRemote, remoteHead, safeBackupIfDirty };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function hasHandlers(appDir: string): boolean;
|
|
2
|
+
/** Other features may add top-level fields, so writes merge into the existing file instead of replacing it. */
|
|
3
|
+
export declare function writeUsageJson(appDir: string, patch: Record<string, unknown>): Promise<void>;
|
|
4
|
+
/** Refreshes the git-tracked `usage.json` and commits it if it changed, so a deploy's push carries it. */
|
|
5
|
+
export declare function updateOrCreateUsageJson(appDir: string, appName: string, deployedBy: string): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=usage-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-json.d.ts","sourceRoot":"","sources":["../../../src/util/usage-json.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,+GAA+G;AAC/G,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAWlG;AAED,0GAA0G;AAC1G,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhH"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readJson, writeJson } from 'fs-extra';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { readHandlers } from '../backend/handlers.mjs';
|
|
6
|
+
import { execGit } from './git/repo.mjs';
|
|
7
|
+
|
|
8
|
+
function hasHandlers(appDir) {
|
|
9
|
+
return existsSync(path.join(appDir, 'backend', 'handlers'));
|
|
10
|
+
}
|
|
11
|
+
/** Other features may add top-level fields, so writes merge into the existing file instead of replacing it. */
|
|
12
|
+
async function writeUsageJson(appDir, patch) {
|
|
13
|
+
const file = path.join(appDir, 'usage.json');
|
|
14
|
+
let existing = {};
|
|
15
|
+
try {
|
|
16
|
+
existing = await readJson(file);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err.code !== 'ENOENT') {
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
await writeJson(file, { ...existing, ...patch }, { spaces: 2 });
|
|
24
|
+
}
|
|
25
|
+
/** Refreshes the git-tracked `usage.json` and commits it if it changed, so a deploy's push carries it. */
|
|
26
|
+
async function updateOrCreateUsageJson(appDir, appName, deployedBy) {
|
|
27
|
+
const apiUsages = hasHandlers(appDir) ? (await readHandlers(appDir)).apiUsages : {};
|
|
28
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
29
|
+
await execGit(['add', 'usage.json'], appDir);
|
|
30
|
+
const { exitCode } = await execa('git', ['diff', '--cached', '--quiet'], { cwd: appDir, reject: false });
|
|
31
|
+
if (exitCode !== 0) {
|
|
32
|
+
await execGit(['commit', '-m', 'Update usage.json'], appDir);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { hasHandlers, updateOrCreateUsageJson, writeUsageJson };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-json.test.d.ts","sourceRoot":"","sources":["../../../src/util/__tests__/usage-json.test.ts"],"names":[],"mappings":""}
|
package/dist/util/git/repo.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
type GitEnv = Record<string, string | undefined>;
|
|
2
|
+
export declare function execGit(args: string[], dir: string, env?: GitEnv): import("execa").ResultPromise<{
|
|
3
|
+
cwd: string;
|
|
4
|
+
env: GitEnv | undefined;
|
|
5
|
+
}>;
|
|
1
6
|
export interface ConflictInfo {
|
|
2
7
|
localCommits: string[];
|
|
3
8
|
remoteCommits: string[];
|
|
@@ -25,4 +30,5 @@ export declare function pullAndResolve(dir: string, env?: Record<string, string>
|
|
|
25
30
|
export declare function pushToRemote(dir: string, env?: Record<string, string>): Promise<void>;
|
|
26
31
|
export declare function remoteHead(dir: string): Promise<string>;
|
|
27
32
|
export declare function localHead(dir: string): Promise<string>;
|
|
33
|
+
export {};
|
|
28
34
|
//# sourceMappingURL=repo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/util/git/repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../src/util/git/repo.ts"],"names":[],"mappings":"AAOA,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;;;GAEhE;AAQD,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAKD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI3E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5G;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3F;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5D"}
|
package/dist/util/git/repo.js
CHANGED
|
@@ -180,6 +180,7 @@ async function ensureGitignore(dir) {
|
|
|
180
180
|
|
|
181
181
|
exports.cloneRepo = cloneRepo;
|
|
182
182
|
exports.defaultS3Remote = defaultS3Remote;
|
|
183
|
+
exports.execGit = execGit;
|
|
183
184
|
exports.getRemoteUrl = getRemoteUrl;
|
|
184
185
|
exports.guardCleanTree = guardCleanTree;
|
|
185
186
|
exports.initRepo = initRepo;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function hasHandlers(appDir: string): boolean;
|
|
2
|
+
/** Other features may add top-level fields, so writes merge into the existing file instead of replacing it. */
|
|
3
|
+
export declare function writeUsageJson(appDir: string, patch: Record<string, unknown>): Promise<void>;
|
|
4
|
+
/** Refreshes the git-tracked `usage.json` and commits it if it changed, so a deploy's push carries it. */
|
|
5
|
+
export declare function updateOrCreateUsageJson(appDir: string, appName: string, deployedBy: string): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=usage-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-json.d.ts","sourceRoot":"","sources":["../../src/util/usage-json.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,+GAA+G;AAC/G,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAWlG;AAED,0GAA0G;AAC1G,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhH"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const fs$1 = require('fs-extra');
|
|
6
|
+
const execa = require('execa');
|
|
7
|
+
const backend_handlers = require('../backend/handlers.js');
|
|
8
|
+
const util_git_repo = require('./git/repo.js');
|
|
9
|
+
|
|
10
|
+
const _interopDefault = e => e && e.__esModule ? e : { default: e };
|
|
11
|
+
|
|
12
|
+
const path__default = /*#__PURE__*/_interopDefault(path);
|
|
13
|
+
|
|
14
|
+
function hasHandlers(appDir) {
|
|
15
|
+
return fs.existsSync(path__default.default.join(appDir, 'backend', 'handlers'));
|
|
16
|
+
}
|
|
17
|
+
/** Other features may add top-level fields, so writes merge into the existing file instead of replacing it. */
|
|
18
|
+
async function writeUsageJson(appDir, patch) {
|
|
19
|
+
const file = path__default.default.join(appDir, 'usage.json');
|
|
20
|
+
let existing = {};
|
|
21
|
+
try {
|
|
22
|
+
existing = await fs$1.readJson(file);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err.code !== 'ENOENT') {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
await fs$1.writeJson(file, { ...existing, ...patch }, { spaces: 2 });
|
|
30
|
+
}
|
|
31
|
+
/** Refreshes the git-tracked `usage.json` and commits it if it changed, so a deploy's push carries it. */
|
|
32
|
+
async function updateOrCreateUsageJson(appDir, appName, deployedBy) {
|
|
33
|
+
const apiUsages = hasHandlers(appDir) ? (await backend_handlers.readHandlers(appDir)).apiUsages : {};
|
|
34
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
35
|
+
await util_git_repo.execGit(['add', 'usage.json'], appDir);
|
|
36
|
+
const { exitCode } = await execa.execa('git', ['diff', '--cached', '--quiet'], { cwd: appDir, reject: false });
|
|
37
|
+
if (exitCode !== 0) {
|
|
38
|
+
await util_git_repo.execGit(['commit', '-m', 'Update usage.json'], appDir);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.hasHandlers = hasHandlers;
|
|
43
|
+
exports.updateOrCreateUsageJson = updateOrCreateUsageJson;
|
|
44
|
+
exports.writeUsageJson = writeUsageJson;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import { mkdtemp, rm, ensureDir, writeFile
|
|
3
|
+
import { mkdtemp, rm, ensureDir, writeFile } from 'fs-extra';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
|
-
import { deployBackend
|
|
6
|
+
import { deployBackend } from '../backend';
|
|
7
|
+
import { hasHandlers } from '../../util/usage-json';
|
|
7
8
|
import { readHandlers } from '../../backend/handlers';
|
|
8
9
|
import { DeployError } from '../../util/errors';
|
|
9
10
|
|
|
@@ -63,7 +64,7 @@ describe('deployBackend (live)', () => {
|
|
|
63
64
|
await rm(tmp, { recursive: true, force: true });
|
|
64
65
|
});
|
|
65
66
|
|
|
66
|
-
it('skips upload
|
|
67
|
+
it('skips upload when there is no handlers directory', async () => {
|
|
67
68
|
// hasHandlers is false here, so readHandlers is never reached — no mock needed.
|
|
68
69
|
await expect(
|
|
69
70
|
deployBackend({
|
|
@@ -76,16 +77,10 @@ describe('deployBackend (live)', () => {
|
|
|
76
77
|
})
|
|
77
78
|
).resolves.toEqual({ integrations: [], handlerNames: [], apiUsages: {} });
|
|
78
79
|
expect(mockSend).not.toHaveBeenCalled();
|
|
79
|
-
await expect(readJson(path.join(tmp, 'usage.json'))).resolves.toEqual({
|
|
80
|
-
appName: 'mf-my-app',
|
|
81
|
-
deployedBy: 'test-user',
|
|
82
|
-
apiUsages: {},
|
|
83
|
-
});
|
|
84
80
|
});
|
|
85
81
|
|
|
86
|
-
it('skips upload
|
|
82
|
+
it('skips upload when backend/handlers exists but has no handler sources', async () => {
|
|
87
83
|
await ensureDir(path.join(tmp, 'backend', 'handlers'));
|
|
88
|
-
await writeJson(path.join(tmp, 'usage.json'), { apiUsages: { deleted_handler: {} }, keepMe: 1 });
|
|
89
84
|
readHandlersMock.mockResolvedValue({ handlers: {}, apiDomains: [], apiUsages: {} });
|
|
90
85
|
|
|
91
86
|
await expect(
|
|
@@ -99,12 +94,6 @@ describe('deployBackend (live)', () => {
|
|
|
99
94
|
})
|
|
100
95
|
).resolves.toEqual({ integrations: [], handlerNames: [], apiUsages: {} });
|
|
101
96
|
expect(mockSend).not.toHaveBeenCalled();
|
|
102
|
-
await expect(readJson(path.join(tmp, 'usage.json'))).resolves.toEqual({
|
|
103
|
-
keepMe: 1,
|
|
104
|
-
appName: 'mf-my-app',
|
|
105
|
-
deployedBy: 'test-user',
|
|
106
|
-
apiUsages: {},
|
|
107
|
-
});
|
|
108
97
|
});
|
|
109
98
|
|
|
110
99
|
it('transpiles and uploads handlers when backend/handlers exists', async () => {
|
|
@@ -144,54 +133,6 @@ describe('deployBackend (live)', () => {
|
|
|
144
133
|
);
|
|
145
134
|
const meta = JSON.parse((metaCall![0] as { input: { Body: string } }).input.Body);
|
|
146
135
|
expect(meta).toEqual({ version: 1, deployedBy: 'test-user', handlers: ['call_snowflake'] });
|
|
147
|
-
|
|
148
|
-
await expect(readJson(path.join(tmp, 'usage.json'))).resolves.toEqual({
|
|
149
|
-
appName: 'mf-my-app',
|
|
150
|
-
deployedBy: 'test-user',
|
|
151
|
-
apiUsages: { call_snowflake: { snowflake: { query: [{ sql: 'x' }] } } },
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('writes usage.json locally even for apps that had none yet', async () => {
|
|
156
|
-
await ensureDir(path.join(tmp, 'backend', 'handlers'));
|
|
157
|
-
await expect(pathExists(path.join(tmp, 'usage.json'))).resolves.toBe(false);
|
|
158
|
-
|
|
159
|
-
await deployBackend({
|
|
160
|
-
appDir: tmp,
|
|
161
|
-
appName: 'mf-my-app',
|
|
162
|
-
mfKeyPrefix: 'mf-my-app/1',
|
|
163
|
-
version: 1,
|
|
164
|
-
deployedBy: 'test-user',
|
|
165
|
-
isPreview: false,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
expect(mockSend).toHaveBeenCalled();
|
|
169
|
-
await expect(readJson(path.join(tmp, 'usage.json'))).resolves.toEqual({
|
|
170
|
-
appName: 'mf-my-app',
|
|
171
|
-
deployedBy: 'test-user',
|
|
172
|
-
apiUsages: { call_snowflake: {} },
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('merges the local usage.json instead of overwriting it, and writes the merged result back locally', async () => {
|
|
177
|
-
await ensureDir(path.join(tmp, 'backend', 'handlers'));
|
|
178
|
-
await writeJson(path.join(tmp, 'usage.json'), { someOtherFeatureField: 'keep-me' });
|
|
179
|
-
|
|
180
|
-
await deployBackend({
|
|
181
|
-
appDir: tmp,
|
|
182
|
-
appName: 'mf-my-app',
|
|
183
|
-
mfKeyPrefix: 'mf-my-app/1',
|
|
184
|
-
version: 1,
|
|
185
|
-
deployedBy: 'test-user',
|
|
186
|
-
isPreview: false,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
await expect(readJson(path.join(tmp, 'usage.json'))).resolves.toEqual({
|
|
190
|
-
someOtherFeatureField: 'keep-me',
|
|
191
|
-
appName: 'mf-my-app',
|
|
192
|
-
deployedBy: 'test-user',
|
|
193
|
-
apiUsages: { call_snowflake: {} },
|
|
194
|
-
});
|
|
195
136
|
});
|
|
196
137
|
|
|
197
138
|
it('tags a handler upload failure as deploy_backend_error, not register_error', async () => {
|
|
@@ -17,6 +17,7 @@ import * as brokerProvider from '../../util/auth/broker-credential-provider';
|
|
|
17
17
|
import * as brokerApp from '../../util/broker/app';
|
|
18
18
|
import * as authStore from '../../util/auth/auth-store';
|
|
19
19
|
import * as gitRepo from '../../util/git/repo';
|
|
20
|
+
import { updateOrCreateUsageJson } from '../../util/usage-json';
|
|
20
21
|
|
|
21
22
|
vi.mock('../build', () => ({
|
|
22
23
|
buildCommand: vi.fn(),
|
|
@@ -67,6 +68,11 @@ vi.mock('../../util/broker/app', () => ({
|
|
|
67
68
|
fetchApp: vi.fn().mockResolvedValue(STUB_MASTER_APP),
|
|
68
69
|
}));
|
|
69
70
|
|
|
71
|
+
vi.mock('../../util/usage-json', async importOriginal => ({
|
|
72
|
+
...(await importOriginal<typeof import('../../util/usage-json')>()),
|
|
73
|
+
updateOrCreateUsageJson: vi.fn(),
|
|
74
|
+
}));
|
|
75
|
+
|
|
70
76
|
vi.mock('../../util/git/repo', () => ({
|
|
71
77
|
getRemoteUrl: vi.fn(),
|
|
72
78
|
guardCleanTree: vi.fn(),
|
|
@@ -218,6 +224,7 @@ describe('deploy --preview', () => {
|
|
|
218
224
|
expect(gitRepo.guardCleanTree).not.toHaveBeenCalled();
|
|
219
225
|
expect(gitRepo.pullAndResolve).not.toHaveBeenCalled();
|
|
220
226
|
expect(gitRepo.pushToRemote).not.toHaveBeenCalled();
|
|
227
|
+
expect(updateOrCreateUsageJson).not.toHaveBeenCalled();
|
|
221
228
|
expect(gitRepo.initRepo).not.toHaveBeenCalled();
|
|
222
229
|
});
|
|
223
230
|
|
|
@@ -405,6 +412,16 @@ describe('deploy failure reasons', () => {
|
|
|
405
412
|
expect((await failedDeploy()).reason).toBe('broker_error');
|
|
406
413
|
});
|
|
407
414
|
|
|
415
|
+
it('updates usage.json before pushing on a live deploy', async () => {
|
|
416
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => '' }));
|
|
417
|
+
vi.mocked(updateOrCreateUsageJson).mockReset().mockResolvedValue(undefined);
|
|
418
|
+
vi.mocked(gitRepo.pushToRemote).mockReset().mockRejectedValue(new Error('stop after push'));
|
|
419
|
+
await deployCommand().catch(() => {});
|
|
420
|
+
const [usageOrder] = vi.mocked(updateOrCreateUsageJson).mock.invocationCallOrder;
|
|
421
|
+
const [pushOrder] = vi.mocked(gitRepo.pushToRemote).mock.invocationCallOrder;
|
|
422
|
+
expect(usageOrder).toBeLessThan(pushOrder);
|
|
423
|
+
});
|
|
424
|
+
|
|
408
425
|
// buildCommand throws plain errors (see commands/__tests__/build.test.ts), so a build
|
|
409
426
|
// failure surfaces as untagged here — see the propagation test below. The STS 401/403 →
|
|
410
427
|
// auth tagging lives in broker-credential-provider.ts — see its own test file.
|
package/src/commands/backend.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
4
|
-
import { readJson
|
|
3
|
+
import { readJson } from 'fs-extra';
|
|
5
4
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
6
5
|
import { info, output } from '../util/logger';
|
|
7
6
|
import { mfAppName, getCwdAppName } from '../util/app-name';
|
|
@@ -14,6 +13,7 @@ import { runSandbox } from '../backend/client';
|
|
|
14
13
|
import { formatSandboxRun } from '../backend/format-sandbox-run';
|
|
15
14
|
import { validateHandlerSource } from '../backend/validate';
|
|
16
15
|
import { tagDeployError } from '../util/errors';
|
|
16
|
+
import { hasHandlers, writeUsageJson } from '../util/usage-json';
|
|
17
17
|
|
|
18
18
|
function currentAppName(): string {
|
|
19
19
|
const raw = getCwdAppName();
|
|
@@ -23,28 +23,6 @@ function currentAppName(): string {
|
|
|
23
23
|
return mfAppName(raw);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function hasHandlers(appDir: string): boolean {
|
|
27
|
-
return existsSync(path.join(appDir, 'backend', 'handlers'));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Other features may add top-level fields, so writes below merge into this copy instead of replacing it. */
|
|
31
|
-
async function readExistingUsage(appDir: string): Promise<Record<string, unknown>> {
|
|
32
|
-
try {
|
|
33
|
-
return (await readJson(path.join(appDir, 'usage.json'))) as Record<string, unknown>;
|
|
34
|
-
} catch (err) {
|
|
35
|
-
if ((err as { code?: string }).code === 'ENOENT') {
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
throw err;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Keeps the app's git-tracked `usage.json` current with every handler parse (validate or deploy). */
|
|
43
|
-
async function writeUsageJson(appDir: string, patch: Record<string, unknown>): Promise<void> {
|
|
44
|
-
const existing = await readExistingUsage(appDir);
|
|
45
|
-
await writeJson(path.join(appDir, 'usage.json'), { ...existing, ...patch }, { spaces: 2 });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
26
|
export interface ReadDetectAndUploadResult {
|
|
49
27
|
integrations: string[];
|
|
50
28
|
handlerNames: string[];
|
|
@@ -60,14 +38,12 @@ async function readDetectAndUpload(
|
|
|
60
38
|
logLabel: string
|
|
61
39
|
): Promise<ReadDetectAndUploadResult> {
|
|
62
40
|
if (!hasHandlers(appDir)) {
|
|
63
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages: {} });
|
|
64
41
|
return { integrations: [], handlerNames: [], apiUsages: {} };
|
|
65
42
|
}
|
|
66
43
|
info(`reading backend handlers ${logLabel}`);
|
|
67
44
|
const { handlers, apiDomains, apiUsages } = await readHandlers(appDir);
|
|
68
45
|
const integrations = detectIntegrations(apiDomains);
|
|
69
46
|
const handlerNames = Object.keys(handlers);
|
|
70
|
-
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
71
47
|
if (Object.keys(handlers).length === 0) {
|
|
72
48
|
return { integrations, handlerNames, apiUsages };
|
|
73
49
|
}
|
package/src/commands/deploy.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { brokerCredentialProvider, fetchDeployVersion } from '../util/auth/broke
|
|
|
15
15
|
import { readZthAuth } from '../util/auth/auth-store';
|
|
16
16
|
import { cleanAppName, mfAppName, validateAppName, buildPreviewSlug } from '../util/app-name';
|
|
17
17
|
import { getRemoteUrl, guardCleanTree, pullAndResolve, pushToRemote, initRepo } from '../util/git/repo';
|
|
18
|
+
import { updateOrCreateUsageJson } from '../util/usage-json';
|
|
18
19
|
import { resolveGitEnv } from '../util/git/git-env';
|
|
19
20
|
import { type RemoteType, validateRemoteOptions, resolveRemoteUrl } from '../util/git/remote-options';
|
|
20
21
|
import { setupS3Client } from '../util/s3/client';
|
|
@@ -288,6 +289,7 @@ async function deployLive({ paths, consumerName, appName, token, opts }: DeployC
|
|
|
288
289
|
await guardCleanTree(process.cwd());
|
|
289
290
|
info('syncing with git remote');
|
|
290
291
|
await pullAndResolve(process.cwd(), gitEnv);
|
|
292
|
+
await updateOrCreateUsageJson(process.cwd(), appName, whoAmI());
|
|
291
293
|
} catch (err) {
|
|
292
294
|
throw tagDeployError('git_error', err);
|
|
293
295
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { mkdtemp, rm, ensureDir, writeJson, readJson } from 'fs-extra';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { updateOrCreateUsageJson } from '../usage-json';
|
|
8
|
+
|
|
9
|
+
vi.mock('../../backend/handlers', () => ({
|
|
10
|
+
readHandlers: vi.fn().mockResolvedValue({ handlers: {}, apiDomains: [], apiUsages: { call_snowflake: {} } }),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('updateOrCreateUsageJson', () => {
|
|
14
|
+
let tmp: string;
|
|
15
|
+
const usagePath = () => path.join(tmp, 'usage.json');
|
|
16
|
+
const run = (deployedBy = 'test-user') => updateOrCreateUsageJson(tmp, 'mf-my-app', deployedBy);
|
|
17
|
+
const commitCount = async () => Number((await execa('git', ['rev-list', '--count', 'HEAD'], { cwd: tmp })).stdout);
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
tmp = await mkdtemp(path.join(os.tmpdir(), 'z2h-usage-'));
|
|
21
|
+
await execa('git', ['init', '-q'], { cwd: tmp });
|
|
22
|
+
await execa('git', ['config', 'user.email', 't@t.t'], { cwd: tmp });
|
|
23
|
+
await execa('git', ['config', 'user.name', 't'], { cwd: tmp });
|
|
24
|
+
await execa('git', ['commit', '-q', '--allow-empty', '-m', 'init'], { cwd: tmp });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(async () => {
|
|
28
|
+
await rm(tmp, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('creates usage.json from handlers and commits it', async () => {
|
|
32
|
+
await ensureDir(path.join(tmp, 'backend', 'handlers'));
|
|
33
|
+
await run();
|
|
34
|
+
await expect(readJson(usagePath())).resolves.toEqual({
|
|
35
|
+
appName: 'mf-my-app',
|
|
36
|
+
deployedBy: 'test-user',
|
|
37
|
+
apiUsages: { call_snowflake: {} },
|
|
38
|
+
});
|
|
39
|
+
expect(await commitCount()).toBe(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('merges into existing fields and resets apiUsages when there are no handlers', async () => {
|
|
43
|
+
await writeJson(usagePath(), { apiUsages: { deleted_handler: {} }, keepMe: 1 });
|
|
44
|
+
await run();
|
|
45
|
+
await expect(readJson(usagePath())).resolves.toEqual({
|
|
46
|
+
keepMe: 1,
|
|
47
|
+
appName: 'mf-my-app',
|
|
48
|
+
deployedBy: 'test-user',
|
|
49
|
+
apiUsages: {},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('does not commit when nothing changed', async () => {
|
|
54
|
+
await run();
|
|
55
|
+
await run();
|
|
56
|
+
expect(await commitCount()).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/src/util/git/repo.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { DeployError, GitConflictError, messageOf } from '../errors';
|
|
|
7
7
|
|
|
8
8
|
type GitEnv = Record<string, string | undefined>;
|
|
9
9
|
|
|
10
|
-
function execGit(args: string[], dir: string, env?: GitEnv) {
|
|
10
|
+
export function execGit(args: string[], dir: string, env?: GitEnv) {
|
|
11
11
|
return execa('git', args, { cwd: dir, env });
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readJson, writeJson } from 'fs-extra';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { readHandlers } from '../backend/handlers';
|
|
6
|
+
import { execGit } from './git/repo';
|
|
7
|
+
|
|
8
|
+
export function hasHandlers(appDir: string): boolean {
|
|
9
|
+
return existsSync(path.join(appDir, 'backend', 'handlers'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Other features may add top-level fields, so writes merge into the existing file instead of replacing it. */
|
|
13
|
+
export async function writeUsageJson(appDir: string, patch: Record<string, unknown>): Promise<void> {
|
|
14
|
+
const file = path.join(appDir, 'usage.json');
|
|
15
|
+
let existing: Record<string, unknown> = {};
|
|
16
|
+
try {
|
|
17
|
+
existing = await readJson(file);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if ((err as { code?: string }).code !== 'ENOENT') {
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
await writeJson(file, { ...existing, ...patch }, { spaces: 2 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Refreshes the git-tracked `usage.json` and commits it if it changed, so a deploy's push carries it. */
|
|
27
|
+
export async function updateOrCreateUsageJson(appDir: string, appName: string, deployedBy: string): Promise<void> {
|
|
28
|
+
const apiUsages = hasHandlers(appDir) ? (await readHandlers(appDir)).apiUsages : {};
|
|
29
|
+
await writeUsageJson(appDir, { appName, deployedBy, apiUsages });
|
|
30
|
+
await execGit(['add', 'usage.json'], appDir);
|
|
31
|
+
const { exitCode } = await execa('git', ['diff', '--cached', '--quiet'], { cwd: appDir, reject: false });
|
|
32
|
+
if (exitCode !== 0) {
|
|
33
|
+
await execGit(['commit', '-m', 'Update usage.json'], appDir);
|
|
34
|
+
}
|
|
35
|
+
}
|