@isomorph.ai/cli 0.2.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/README.md +55 -0
- package/dist/packages/harbour-cli/src/agent-setup.js +235 -0
- package/dist/packages/harbour-cli/src/app-schema.js +142 -0
- package/dist/packages/harbour-cli/src/auth.js +197 -0
- package/dist/packages/harbour-cli/src/check.js +317 -0
- package/dist/packages/harbour-cli/src/cli.js +321 -0
- package/dist/packages/harbour-cli/src/config.js +97 -0
- package/dist/packages/harbour-cli/src/dev.js +125 -0
- package/dist/packages/harbour-cli/src/forwarder.js +168 -0
- package/dist/packages/harbour-cli/src/integrations.js +385 -0
- package/dist/packages/harbour-cli/src/jobs.js +53 -0
- package/dist/packages/harbour-cli/src/kit-bundle.js +41 -0
- package/dist/packages/harbour-cli/src/kit-bundle.manifest.js +32 -0
- package/dist/packages/harbour-cli/src/kit.js +149 -0
- package/dist/packages/harbour-cli/src/local-runtime.js +526 -0
- package/dist/packages/harbour-cli/src/operations.js +382 -0
- package/dist/packages/harbour-cli/src/output.js +273 -0
- package/dist/packages/harbour-cli/src/productionise.js +508 -0
- package/dist/packages/harbour-cli/src/remote-mcp-client.js +121 -0
- package/dist/packages/harbour-cli/src/retained-checks.js +435 -0
- package/dist/packages/harbour-cli/src/source-inventory.js +239 -0
- package/dist/packages/harbour-cli/src/starter.js +557 -0
- package/dist/packages/harbour-cli/src/upload.js +163 -0
- package/dist/packages/harbour-cli/src/version.js +1 -0
- package/dist/src/analyzer.js +685 -0
- package/dist/src/contracts.js +82 -0
- package/dist/src/digest.js +26 -0
- package/dist/src/secret-paths.js +38 -0
- package/dist/src/source-intake.js +125 -0
- package/package.json +43 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const PROTOCOL_VERSION = "2025-11-25";
|
|
2
|
+
export const WAITING_FOR_DATA_ACCESS_PLAIN_ENGLISH = "Your app uses a company data source that isn't set up for apps yet. I've asked your IT admin — I'll continue automatically once it's approved.";
|
|
3
|
+
export const WAITING_FOR_ANALYSIS_PLAIN_ENGLISH = "Isomorph is checking which runtime settings this app needs. It will continue automatically when that check is available.";
|
|
4
|
+
export function waitingForSecretsPlainEnglish(missingNames) {
|
|
5
|
+
const count = missingNames.length;
|
|
6
|
+
return `Your app uses ${count} key${count === 1 ? "" : "s"} (${missingNames.join(", ")}). Enter them securely in your Isomorph console — I'll continue once they're set.`;
|
|
7
|
+
}
|
|
8
|
+
/** Stage → plain-English templates for the two waiting stages. */
|
|
9
|
+
export const OPERATION_WAITING_STAGE_PLAIN_ENGLISH = {
|
|
10
|
+
WAITING_FOR_DATA_ACCESS: WAITING_FOR_DATA_ACCESS_PLAIN_ENGLISH,
|
|
11
|
+
WAITING_FOR_SECRETS: "Your app needs one or more keys entered securely in your Isomorph console before it can be deployed.",
|
|
12
|
+
WAITING_FOR_ANALYSIS: WAITING_FOR_ANALYSIS_PLAIN_ENGLISH
|
|
13
|
+
};
|
|
14
|
+
export function isRecord(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
export function requireString(object, key) {
|
|
18
|
+
const value = object[key];
|
|
19
|
+
if (typeof value !== "string" || value.length === 0)
|
|
20
|
+
throw new Error(`VALIDATION_FAILED: ${key} must be a non-empty string`);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function assertApplicationGraph(value) {
|
|
24
|
+
if (!isRecord(value))
|
|
25
|
+
throw new Error("VALIDATION_FAILED: graph must be an object");
|
|
26
|
+
if (value.schema !== "isomorph.application-graph/1.0")
|
|
27
|
+
throw new Error("VALIDATION_FAILED: unsupported graph schema");
|
|
28
|
+
requireString(value, "workspaceFingerprint");
|
|
29
|
+
const graphDigest = requireString(value, "graphDigest");
|
|
30
|
+
// Some coding surfaces emit a bare hexadecimal digest while others include
|
|
31
|
+
// the algorithm prefix. Normalize at the Harbour boundary so one client
|
|
32
|
+
// formatting difference cannot invalidate the later upload/save steps.
|
|
33
|
+
if (/^[0-9a-f]{64}$/i.test(graphDigest))
|
|
34
|
+
value.graphDigest = `sha256:${graphDigest}`;
|
|
35
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(value.graphDigest))
|
|
36
|
+
throw new Error("VALIDATION_FAILED: graphDigest must be a SHA-256 digest");
|
|
37
|
+
if (!isRecord(value.source))
|
|
38
|
+
throw new Error("VALIDATION_FAILED: source is required");
|
|
39
|
+
requireString(value.source, "revision");
|
|
40
|
+
if (!isRecord(value.deploymentScope))
|
|
41
|
+
throw new Error("VALIDATION_FAILED: deploymentScope is required");
|
|
42
|
+
if (!Array.isArray(value.deploymentScope.includedFiles) || value.deploymentScope.includedFiles.length === 0) {
|
|
43
|
+
throw new Error("VALIDATION_FAILED: deploymentScope.includedFiles must include at least one file");
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(value.components) || value.components.length === 0)
|
|
46
|
+
throw new Error("VALIDATION_FAILED: at least one component is required");
|
|
47
|
+
const serialized = JSON.stringify(value);
|
|
48
|
+
if (serialized.length > 65_536)
|
|
49
|
+
throw new Error("MANIFEST_TOO_LARGE: application graph exceeds 64 KiB");
|
|
50
|
+
// Environment-variable names are metadata, not secret material. Apps commonly
|
|
51
|
+
// declare names such as HIGGSFIELD_ACCESS_TOKEN or SESSION_COOKIE_SECRET; a
|
|
52
|
+
// whole-document substring scan incorrectly rejected those safe declarations.
|
|
53
|
+
// Reject only prohibited field names and assignment-shaped marker values.
|
|
54
|
+
if (containsRawSecretMarker(value)) {
|
|
55
|
+
throw new Error("RAW_SECRET_REJECTED: graph contains a prohibited field or value marker");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const RAW_SECRET_FIELDS = new Set(["secret_value", "private_key", "access_token", "refresh_token"]);
|
|
59
|
+
function containsRawSecretMarker(value, parentKey) {
|
|
60
|
+
if (parentKey === "environmentVariableNames")
|
|
61
|
+
return false;
|
|
62
|
+
if (Array.isArray(value))
|
|
63
|
+
return value.some(item => containsRawSecretMarker(item, parentKey));
|
|
64
|
+
if (!isRecord(value)) {
|
|
65
|
+
return typeof value === "string" && /(?:secret_value|private_key|access_token|refresh_token)\s*[:=]/i.test(value);
|
|
66
|
+
}
|
|
67
|
+
return Object.entries(value).some(([key, child]) => {
|
|
68
|
+
if (RAW_SECRET_FIELDS.has(key.toLowerCase()))
|
|
69
|
+
return true;
|
|
70
|
+
return containsRawSecretMarker(child, key);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function assertIdentityProfile(value) {
|
|
74
|
+
if (!isRecord(value) || value.schema !== "harbour.enterprise-identity-profile/1.0") {
|
|
75
|
+
throw new Error("VALIDATION_FAILED: unsupported identity profile schema");
|
|
76
|
+
}
|
|
77
|
+
requireString(value, "profileId");
|
|
78
|
+
requireString(value, "issuerRef");
|
|
79
|
+
requireString(value, "clientIdRef");
|
|
80
|
+
if (!Array.isArray(value.requiredScopes))
|
|
81
|
+
throw new Error("VALIDATION_FAILED: requiredScopes must be an array");
|
|
82
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function stableValue(value) {
|
|
2
|
+
if (Array.isArray(value))
|
|
3
|
+
return value.map(stableValue);
|
|
4
|
+
if (value && typeof value === "object") {
|
|
5
|
+
const output = {};
|
|
6
|
+
for (const key of Object.keys(value).sort()) {
|
|
7
|
+
output[key] = stableValue(value[key]);
|
|
8
|
+
}
|
|
9
|
+
return output;
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function stableJson(value) {
|
|
14
|
+
return JSON.stringify(stableValue(value));
|
|
15
|
+
}
|
|
16
|
+
export async function sha256(value) {
|
|
17
|
+
const bytes = new TextEncoder().encode(typeof value === "string" ? value : stableJson(value));
|
|
18
|
+
return sha256Bytes(bytes);
|
|
19
|
+
}
|
|
20
|
+
export async function sha256Bytes(bytes) {
|
|
21
|
+
const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes).buffer);
|
|
22
|
+
return `sha256:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
23
|
+
}
|
|
24
|
+
export async function verifyDigest(value, expected) {
|
|
25
|
+
return (await sha256(value)) === expected;
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checked-in environment templates are safe source metadata. Real env files
|
|
3
|
+
* remain excluded because they can contain credentials. Keep this predicate
|
|
4
|
+
* shared by scanning, manifest validation, and the CLI archive guard so a
|
|
5
|
+
* template named `.env.mysql.example` is treated consistently everywhere.
|
|
6
|
+
*/
|
|
7
|
+
export function isEnvExamplePath(path) {
|
|
8
|
+
return /(^|\/)(?:\.env(?:\.[^/]+)*)\.(?:example|sample|template|dist)$/i.test(path);
|
|
9
|
+
}
|
|
10
|
+
export function isProhibitedSecretPath(path) {
|
|
11
|
+
if (isEnvExamplePath(path))
|
|
12
|
+
return false;
|
|
13
|
+
return /(^|\/)(?:\.env(?:\.[^/]+)?|\.npmrc|id_rsa|[^/]+\.(?:pem|key))$/i.test(path);
|
|
14
|
+
}
|
|
15
|
+
/** Files the intake worker intentionally leaves out of the Git-bound set. */
|
|
16
|
+
export function isSourceIntakeExcludedPath(path) {
|
|
17
|
+
return /(^|\/)(?:__MACOSX)(?:\/|$)|(^|\/)\.DS_Store$/i.test(path)
|
|
18
|
+
|| /(^|\/)\.github\/workflows\//i.test(path);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The development kit commits exactly these files under `.isomorph`: the
|
|
22
|
+
* integrations declaration, the kit lock, the governed-AI inventory, and the
|
|
23
|
+
* journey checks directly in `.isomorph/checks/` as `.mjs`, `.js` or `.cjs`
|
|
24
|
+
* modules (no subdirectories, no other extensions — evidence and reports
|
|
25
|
+
* written next to them stay local). Everything else there (local state,
|
|
26
|
+
* generated control files) stays local.
|
|
27
|
+
*/
|
|
28
|
+
export function isKitCommittedPath(path) {
|
|
29
|
+
return /(^|\/)\.isomorph\/(?:integrations\.json|kit\.lock\.json|ai-inventory\.json|checks\/[^/]+\.(?:mjs|js|cjs))$/.test(path);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dependency and internal directories the intake never stages: `node_modules`,
|
|
33
|
+
* `.git`, the pipeline's own `.harbour` (receipts and derived artifacts, never
|
|
34
|
+
* an app's), and `.isomorph` except its committed kit files.
|
|
35
|
+
*/
|
|
36
|
+
export function isInternalSourcePath(path) {
|
|
37
|
+
return /(^|\/)(?:node_modules|\.git|\.harbour)(?:\/|$)/i.test(path) || (/(^|\/)\.isomorph(?:\/|$)/i.test(path) && !isKitCommittedPath(path));
|
|
38
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { sha256, sha256Bytes, stableJson } from "./digest.js";
|
|
2
|
+
import { isInternalSourcePath, isProhibitedSecretPath } from "./secret-paths.js";
|
|
3
|
+
export class MemorySourceArchiveStore {
|
|
4
|
+
archives = new Map();
|
|
5
|
+
async put(objectKey, archive) { this.archives.set(objectKey, clone(archive)); }
|
|
6
|
+
async get(objectKey) {
|
|
7
|
+
const value = this.archives.get(objectKey);
|
|
8
|
+
return value ? clone(value) : undefined;
|
|
9
|
+
}
|
|
10
|
+
async exists(objectKey) { return this.archives.has(objectKey); }
|
|
11
|
+
async delete(objectKey) { this.archives.delete(objectKey); }
|
|
12
|
+
}
|
|
13
|
+
export async function createSourceManifest(input) {
|
|
14
|
+
const files = await Promise.all(input.files.map(async (file) => {
|
|
15
|
+
const bytes = toBytes(file.content);
|
|
16
|
+
return { path: file.path, sha256: await sha256Bytes(bytes), bytes: bytes.byteLength };
|
|
17
|
+
}));
|
|
18
|
+
const draft = { schema: "harbour.source-manifest/1.0", tenantId: input.tenantId, appId: input.appId, operationId: input.operationId, graphDigest: input.graphDigest, files };
|
|
19
|
+
const manifest = { ...draft, manifestDigest: await sha256(draft) };
|
|
20
|
+
validateSourceManifest(manifest);
|
|
21
|
+
return manifest;
|
|
22
|
+
}
|
|
23
|
+
export async function createSourceArchive(manifest, files) {
|
|
24
|
+
validateSourceManifest(manifest);
|
|
25
|
+
const encodedFiles = files.map(file => ({ path: file.path, contentBase64: bytesToBase64(toBytes(file.content)) }));
|
|
26
|
+
const draft = { schema: "isomorph.source-archive/1.0", manifest, files: encodedFiles };
|
|
27
|
+
const archive = { ...draft, archiveDigest: await sha256(draft) };
|
|
28
|
+
await validateSourceArchive(archive);
|
|
29
|
+
return archive;
|
|
30
|
+
}
|
|
31
|
+
export function createSourceUploadIntent(input) {
|
|
32
|
+
validateSourceManifest(input.manifest);
|
|
33
|
+
if (input.manifest.tenantId !== input.tenantId || input.manifest.appId !== input.appId || input.manifest.operationId !== input.operationId) {
|
|
34
|
+
throw new Error("SOURCE_UPLOAD_INTENT_SCOPE_MISMATCH: manifest does not belong to this tenant, app, and operation.");
|
|
35
|
+
}
|
|
36
|
+
const now = input.now ?? new Date();
|
|
37
|
+
const expiry = new Date(now.getTime() + (input.ttlMs ?? 15 * 60_000));
|
|
38
|
+
const nonce = crypto.randomUUID();
|
|
39
|
+
return {
|
|
40
|
+
schema: "harbour.source-upload-intent/1.0",
|
|
41
|
+
intentId: `upload-${nonce}`,
|
|
42
|
+
tenantId: input.tenantId,
|
|
43
|
+
appId: input.appId,
|
|
44
|
+
operationId: input.operationId,
|
|
45
|
+
objectKey: `source-staging/${input.tenantId}/${input.appId}/${input.operationId}/${nonce}.json`,
|
|
46
|
+
manifestDigest: input.manifest.manifestDigest,
|
|
47
|
+
preparedAt: now.toISOString(),
|
|
48
|
+
expiresAt: expiry.toISOString(),
|
|
49
|
+
used: false
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export async function acceptSourceUpload(input) {
|
|
53
|
+
const { intent, archive } = input;
|
|
54
|
+
if (intent.schema !== "harbour.source-upload-intent/1.0" || intent.used)
|
|
55
|
+
throw new Error("SOURCE_UPLOAD_INTENT_USED: upload intent is no longer valid.");
|
|
56
|
+
if (new Date(intent.expiresAt).getTime() <= (input.now ?? new Date()).getTime())
|
|
57
|
+
throw new Error("SOURCE_UPLOAD_INTENT_EXPIRED: upload intent has expired.");
|
|
58
|
+
await validateSourceArchive(archive);
|
|
59
|
+
if (archive.manifest.manifestDigest !== intent.manifestDigest
|
|
60
|
+
|| archive.manifest.tenantId !== intent.tenantId
|
|
61
|
+
|| archive.manifest.appId !== intent.appId
|
|
62
|
+
|| archive.manifest.operationId !== intent.operationId) {
|
|
63
|
+
throw new Error("SOURCE_UPLOAD_INTENT_SCOPE_MISMATCH: archive does not match the upload intent.");
|
|
64
|
+
}
|
|
65
|
+
await input.store.put(intent.objectKey, archive);
|
|
66
|
+
return { ...intent, used: true };
|
|
67
|
+
}
|
|
68
|
+
export function validateSourceManifest(manifest) {
|
|
69
|
+
if (manifest.schema !== "harbour.source-manifest/1.0" || !safeId(manifest.tenantId) || !safeId(manifest.appId) || !safeId(manifest.operationId)) {
|
|
70
|
+
throw new Error("SOURCE_MANIFEST_INVALID: manifest identity is invalid.");
|
|
71
|
+
}
|
|
72
|
+
if (!/^sha256:[a-f0-9]{64}$/i.test(manifest.graphDigest) || !/^sha256:[a-f0-9]{64}$/i.test(manifest.manifestDigest) || manifest.files.length === 0) {
|
|
73
|
+
throw new Error("SOURCE_MANIFEST_INVALID: manifest must have a digest and at least one file.");
|
|
74
|
+
}
|
|
75
|
+
const paths = new Set();
|
|
76
|
+
for (const file of manifest.files) {
|
|
77
|
+
if (!safeSourcePath(file.path) || paths.has(file.path) || !/^sha256:[a-f0-9]{64}$/i.test(file.sha256) || !Number.isSafeInteger(file.bytes) || file.bytes < 0) {
|
|
78
|
+
throw new Error("SOURCE_MANIFEST_INVALID: manifest contains an unsafe or duplicate file.");
|
|
79
|
+
}
|
|
80
|
+
paths.add(file.path);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function validateSourceArchive(archive) {
|
|
84
|
+
if (archive.schema !== "isomorph.source-archive/1.0")
|
|
85
|
+
throw new Error("SOURCE_ARCHIVE_INVALID: unsupported archive schema.");
|
|
86
|
+
validateSourceManifest(archive.manifest);
|
|
87
|
+
const expectedArchiveDigest = await sha256({ schema: archive.schema, manifest: archive.manifest, files: archive.files });
|
|
88
|
+
if (archive.archiveDigest !== expectedArchiveDigest)
|
|
89
|
+
throw new Error("SOURCE_ARCHIVE_DIGEST_MISMATCH: archive digest does not match its contents.");
|
|
90
|
+
const expectedManifestDigest = await sha256({ schema: archive.manifest.schema, tenantId: archive.manifest.tenantId, appId: archive.manifest.appId, operationId: archive.manifest.operationId, graphDigest: archive.manifest.graphDigest, files: archive.manifest.files });
|
|
91
|
+
if (archive.manifest.manifestDigest !== expectedManifestDigest)
|
|
92
|
+
throw new Error("SOURCE_MANIFEST_DIGEST_MISMATCH: manifest digest does not match its contents.");
|
|
93
|
+
if (archive.files.length !== archive.manifest.files.length)
|
|
94
|
+
throw new Error("SOURCE_ARCHIVE_INCOMPLETE: archive file count does not match its manifest.");
|
|
95
|
+
const files = new Map(archive.files.map(file => [file.path, file]));
|
|
96
|
+
for (const expected of archive.manifest.files) {
|
|
97
|
+
const actual = files.get(expected.path);
|
|
98
|
+
if (!actual || !safeSourcePath(actual.path))
|
|
99
|
+
throw new Error("SOURCE_ARCHIVE_INCOMPLETE: a manifest file is missing from the archive.");
|
|
100
|
+
const bytes = base64ToBytes(actual.contentBase64);
|
|
101
|
+
if (bytes.byteLength !== expected.bytes || await sha256Bytes(bytes) !== expected.sha256) {
|
|
102
|
+
throw new Error("SOURCE_ARCHIVE_FILE_DIGEST_MISMATCH: archived file does not match the manifest.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Safe to persist as evidence: no source content, archive key, or secret values. */
|
|
107
|
+
export function sourceArchiveEvidence(archive) {
|
|
108
|
+
return { manifestDigest: archive.manifest.manifestDigest, archiveDigest: archive.archiveDigest, fileCount: archive.manifest.files.length, totalBytes: archive.manifest.files.reduce((sum, file) => sum + file.bytes, 0) };
|
|
109
|
+
}
|
|
110
|
+
function safeId(value) { return /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(value); }
|
|
111
|
+
function safeSourcePath(path) {
|
|
112
|
+
return Boolean(path) && !path.startsWith("/") && !path.includes("\\") && !path.split("/").some(part => !part || part === "." || part === "..")
|
|
113
|
+
// Checked-in env templates are configuration metadata, not runtime
|
|
114
|
+
// credentials. All other env variants remain excluded before staging.
|
|
115
|
+
&& !isProhibitedSecretPath(path)
|
|
116
|
+
&& !isInternalSourcePath(path);
|
|
117
|
+
}
|
|
118
|
+
function toBytes(value) { return typeof value === "string" ? new TextEncoder().encode(value) : value; }
|
|
119
|
+
function bytesToBase64(bytes) { return Buffer.from(bytes).toString("base64"); }
|
|
120
|
+
function base64ToBytes(value) {
|
|
121
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value))
|
|
122
|
+
throw new Error("SOURCE_ARCHIVE_INVALID: file is not valid base64.");
|
|
123
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
124
|
+
}
|
|
125
|
+
function clone(value) { return JSON.parse(stableJson(value)); }
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isomorph.ai/cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Isomorph development kit CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"isomorph": "./dist/packages/harbour-cli/src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/Fourier-Labs-AI/harbour-governance-control-plane.git"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/packages/harbour-cli/src",
|
|
18
|
+
"dist/src/analyzer.js",
|
|
19
|
+
"dist/src/contracts.js",
|
|
20
|
+
"dist/src/digest.js",
|
|
21
|
+
"dist/src/secret-paths.js",
|
|
22
|
+
"dist/src/source-intake.js",
|
|
23
|
+
"package.json"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.13.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"embedded-postgres": "18.4.0-beta.17"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prebuild": "node scripts/kit-bundle.mjs verify",
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"kit-bundle:sync": "node scripts/kit-bundle.mjs sync",
|
|
35
|
+
"pack:check": "npm pack --dry-run"
|
|
36
|
+
},
|
|
37
|
+
"harbour": {
|
|
38
|
+
"kitBundle": {
|
|
39
|
+
"repository": "public.ecr.aws/y6t4p3i8/harbour-kit-bundle",
|
|
40
|
+
"version": "0.2.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|