@harperfast/harper 5.2.0 → 5.2.1
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/bin/copyDb.ts +21 -4
- package/bin/harper.ts +20 -51
- package/bin/help.ts +216 -0
- package/components/Application.ts +236 -46
- package/components/ApplicationScope.ts +26 -0
- package/components/EntryHandler.ts +410 -105
- package/components/RuntimeModuleTracker.ts +189 -0
- package/components/Scope.ts +68 -24
- package/components/componentLoader.ts +70 -16
- package/components/deployLifecycle.ts +119 -33
- package/components/mcp/tools/operations.ts +6 -0
- package/components/operations.js +4 -6
- package/config/configUtils.ts +12 -9
- package/config-root.schema.json +10 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +26 -5
- package/dataLayer/hdbInfoController.ts +8 -0
- package/dataLayer/schemaDescribe.ts +2 -1
- package/dist/bin/copyDb.js +13 -2
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/harper.d.ts +6 -0
- package/dist/bin/harper.js +18 -50
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.d.ts +8 -0
- package/dist/bin/help.js +192 -0
- package/dist/bin/help.js.map +1 -0
- package/dist/components/Application.d.ts +16 -1
- package/dist/components/Application.js +210 -38
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +7 -0
- package/dist/components/ApplicationScope.js +22 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.d.ts +4 -4
- package/dist/components/EntryHandler.js +386 -95
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.d.ts +11 -0
- package/dist/components/RuntimeModuleTracker.js +189 -0
- package/dist/components/RuntimeModuleTracker.js.map +1 -0
- package/dist/components/Scope.d.ts +1 -0
- package/dist/components/Scope.js +69 -22
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +69 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deployLifecycle.d.ts +6 -2
- package/dist/components/deployLifecycle.js +109 -31
- package/dist/components/deployLifecycle.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +6 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/operations.js +4 -6
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.js +13 -9
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +12 -5
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.js +4 -0
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +55 -0
- package/dist/resources/DatabaseTransaction.js +282 -103
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js +22 -4
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -5
- package/dist/resources/Table.js +92 -15
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -6
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/blob.d.ts +0 -1
- package/dist/resources/blob.js +15 -7
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +22 -5
- package/dist/resources/databases.js +118 -9
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/jsResource.d.ts +4 -26
- package/dist/resources/jsResource.js +5 -59
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -1
- package/dist/resources/models/Models.js +10 -1
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +9 -0
- package/dist/resources/models/backendRegistry.js +10 -0
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +16 -1
- package/dist/resources/models/openaiStream.js +113 -21
- package/dist/resources/models/openaiStream.js.map +1 -1
- package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
- package/dist/resources/models/v1/chatCompletions.js +115 -0
- package/dist/resources/models/v1/chatCompletions.js.map +1 -0
- package/dist/resources/models/v1/embeddings.d.ts +11 -0
- package/dist/resources/models/v1/embeddings.js +71 -0
- package/dist/resources/models/v1/embeddings.js.map +1 -0
- package/dist/resources/models/v1/errors.d.ts +54 -0
- package/dist/resources/models/v1/errors.js +130 -0
- package/dist/resources/models/v1/errors.js.map +1 -0
- package/dist/resources/models/v1/index.d.ts +36 -0
- package/dist/resources/models/v1/index.js +75 -0
- package/dist/resources/models/v1/index.js.map +1 -0
- package/dist/resources/models/v1/models.d.ts +26 -0
- package/dist/resources/models/v1/models.js +44 -0
- package/dist/resources/models/v1/models.js.map +1 -0
- package/dist/resources/models/v1/translation.d.ts +133 -0
- package/dist/resources/models/v1/translation.js +298 -0
- package/dist/resources/models/v1/translation.js.map +1 -0
- package/dist/resources/roles.d.ts +1 -1
- package/dist/resources/roles.js +54 -7
- package/dist/resources/roles.js.map +1 -1
- package/dist/security/jsLoader.js +84 -33
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/role.js +4 -0
- package/dist/security/role.js.map +1 -1
- package/dist/security/superUserGuard.d.ts +7 -0
- package/dist/security/superUserGuard.js +23 -0
- package/dist/security/superUserGuard.js.map +1 -0
- package/dist/security/tokenAuthentication.d.ts +0 -1
- package/dist/security/tokenAuthentication.js +6 -2
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.d.ts +6 -1
- package/dist/security/user.js +23 -1
- package/dist/security/user.js.map +1 -1
- package/dist/server/http.d.ts +23 -2
- package/dist/server/http.js +99 -12
- package/dist/server/http.js.map +1 -1
- package/dist/server/operationsServer.d.ts +0 -2
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/multipartParser.js +9 -0
- package/dist/server/serverHelpers/multipartParser.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
- package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
- package/dist/server/serverHelpers/registeredOperations.js +14 -13
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +19 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +10 -7
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +57 -18
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +17 -0
- package/dist/server/storageReclamation.js +90 -6
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/socketRouter.js +20 -0
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +12 -0
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/diff/differential.js +7 -3
- package/dist/sqlEngine/diff/differential.js.map +1 -1
- package/dist/sqlTranslator/index.js +6 -1
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/upgradePrompt.d.ts +2 -2
- package/dist/upgrade/upgradePrompt.js +22 -3
- package/dist/upgrade/upgradePrompt.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +1 -0
- package/dist/utility/errors/commonErrors.js +1 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/install/installer.js +32 -0
- package/dist/utility/install/installer.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +7 -0
- package/dist/utility/logging/harper_logger.js +57 -24
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/npm-shrinkwrap.json +197 -193
- package/package.json +9 -7
- package/resources/DatabaseTransaction.ts +310 -101
- package/resources/ResourceInterface.ts +8 -2
- package/resources/Resources.ts +22 -4
- package/resources/Table.ts +321 -243
- package/resources/analytics/write.ts +22 -20
- package/resources/blob.ts +15 -8
- package/resources/databases.ts +123 -12
- package/resources/jsResource.ts +5 -62
- package/resources/models/Models.ts +14 -1
- package/resources/models/backendRegistry.ts +10 -0
- package/resources/models/openaiStream.ts +131 -19
- package/resources/models/v1/chatCompletions.ts +128 -0
- package/resources/models/v1/embeddings.ts +70 -0
- package/resources/models/v1/errors.ts +141 -0
- package/resources/models/v1/index.ts +72 -0
- package/resources/models/v1/models.ts +53 -0
- package/resources/models/v1/translation.ts +362 -0
- package/resources/roles.ts +67 -7
- package/security/jsLoader.ts +84 -30
- package/security/role.ts +7 -0
- package/security/superUserGuard.ts +20 -0
- package/security/tokenAuthentication.ts +6 -3
- package/security/user.ts +26 -1
- package/server/DESIGN.md +45 -34
- package/server/http.ts +100 -13
- package/server/operationsServer.ts +0 -2
- package/server/serverHelpers/multipartParser.ts +9 -0
- package/server/serverHelpers/operationAuthorizationState.ts +11 -0
- package/server/serverHelpers/registeredOperations.ts +19 -15
- package/server/serverHelpers/serverHandlers.js +20 -3
- package/server/serverHelpers/serverUtilities.ts +10 -7
- package/server/static.ts +75 -20
- package/server/storageReclamation.ts +104 -8
- package/server/threads/socketRouter.ts +20 -0
- package/server/threads/threadServer.js +11 -0
- package/sqlTranslator/index.ts +6 -1
- package/static/defaultConfig.yaml +2 -0
- package/studio/web/assets/{Chat-DoVWScmq.js → Chat-aApwhRmz.js} +2 -2
- package/studio/web/assets/{Chat-DoVWScmq.js.map → Chat-aApwhRmz.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js → FloatingChat-sC0H91n9.js} +4 -4
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js.map → FloatingChat-sC0H91n9.js.map} +1 -1
- package/studio/web/assets/{apiToken-BUI_04o7.js → apiToken-DZ8JhHOI.js} +2 -2
- package/studio/web/assets/{apiToken-BUI_04o7.js.map → apiToken-DZ8JhHOI.js.map} +1 -1
- package/studio/web/assets/{applications-D03NA7wW.js → applications-9SEVLIO5.js} +2 -2
- package/studio/web/assets/{applications-D03NA7wW.js.map → applications-9SEVLIO5.js.map} +1 -1
- package/studio/web/assets/{index-Bh_CNAHr.js → index-Dy3uDGXb.js} +6 -6
- package/studio/web/assets/{index-Bh_CNAHr.js.map → index-Dy3uDGXb.js.map} +1 -1
- package/studio/web/assets/{index.lazy-Dx3MpyDC.js → index.lazy-CN1zq4I4.js} +4 -4
- package/studio/web/assets/{index.lazy-Dx3MpyDC.js.map → index.lazy-CN1zq4I4.js.map} +1 -1
- package/studio/web/assets/{notifications-0edoFTsb.js → notifications-BFAF07xr.js} +2 -2
- package/studio/web/assets/{notifications-0edoFTsb.js.map → notifications-BFAF07xr.js.map} +1 -1
- package/studio/web/assets/{notifications-CwKhipK7.js → notifications-CKlYVvVN.js} +2 -2
- package/studio/web/assets/{notifications-CwKhipK7.js.map → notifications-CKlYVvVN.js.map} +1 -1
- package/studio/web/assets/{profile-DUfEPQtx.js → profile-A1zhEdFG.js} +2 -2
- package/studio/web/assets/{profile-DUfEPQtx.js.map → profile-A1zhEdFG.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js → setComponentFile-SEtBt_GV.js} +2 -2
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js.map → setComponentFile-SEtBt_GV.js.map} +1 -1
- package/studio/web/assets/{setup-B56Oz1_u.js → setup-BisINqdH.js} +2 -2
- package/studio/web/assets/{setup-B56Oz1_u.js.map → setup-BisINqdH.js.map} +1 -1
- package/studio/web/assets/{status-BAod7p3o.js → status-BuQoCc7l.js} +2 -2
- package/studio/web/assets/{status-BAod7p3o.js.map → status-BuQoCc7l.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js → swagger-ui-react-CAi_s1PC.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js.map → swagger-ui-react-CAi_s1PC.js.map} +1 -1
- package/studio/web/assets/{tsMode-CrHCRjTK.js → tsMode-DT74tlkM.js} +2 -2
- package/studio/web/assets/{tsMode-CrHCRjTK.js.map → tsMode-DT74tlkM.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js → useEntityRestURL-CGRGc1n7.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js.map → useEntityRestURL-CGRGc1n7.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/upgradePrompt.ts +22 -3
- package/utility/errors/commonErrors.ts +2 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/install/installer.ts +37 -0
- package/utility/logging/harper_logger.ts +57 -26
|
@@ -483,10 +483,106 @@ const COMPONENT_PREPARATION_WAIT_MARGIN_MS = 30000;
|
|
|
483
483
|
const MAX_GIT_EXTRACTION_COMMANDS = 4;
|
|
484
484
|
const MAX_INSTALL_COMMANDS = 2;
|
|
485
485
|
|
|
486
|
+
type ExtractionTransaction = {
|
|
487
|
+
commit(): Promise<void>;
|
|
488
|
+
rollback(): Promise<void>;
|
|
489
|
+
};
|
|
490
|
+
|
|
486
491
|
// The credential helper git executes for a private git-reference deploy. It ships alongside this
|
|
487
492
|
// module (both in source and in dist), holds no secret, and is inert without a live session.
|
|
488
493
|
export const GIT_CREDENTIAL_HELPER_PATH = join(__dirname, 'gitCredentialHelper.js');
|
|
489
494
|
|
|
495
|
+
const PACKAGE_LOCK_FILES = [
|
|
496
|
+
'package-lock.json',
|
|
497
|
+
'npm-shrinkwrap.json',
|
|
498
|
+
'pnpm-lock.yaml',
|
|
499
|
+
'yarn.lock',
|
|
500
|
+
'bun.lock',
|
|
501
|
+
'bun.lockb',
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
type InstalledPackageMetadata = {
|
|
505
|
+
files: Map<string, Buffer>;
|
|
506
|
+
readable: boolean;
|
|
507
|
+
hasLockfile: boolean;
|
|
508
|
+
hasInstallableDependencies: boolean;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
export async function readInstalledPackageMetadata(directory: string): Promise<InstalledPackageMetadata> {
|
|
512
|
+
const files = new Map<string, Buffer>();
|
|
513
|
+
let readable = true;
|
|
514
|
+
let packageJSON: any;
|
|
515
|
+
await Promise.all([
|
|
516
|
+
(async () => {
|
|
517
|
+
try {
|
|
518
|
+
const contents = await readFile(join(directory, 'package.json'));
|
|
519
|
+
try {
|
|
520
|
+
packageJSON = JSON.parse(contents.toString());
|
|
521
|
+
files.set('package.json', Buffer.from(JSON.stringify(canonicalizeJSON(packageJSON))));
|
|
522
|
+
} catch {
|
|
523
|
+
files.set('package.json', contents);
|
|
524
|
+
}
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') readable = false;
|
|
527
|
+
}
|
|
528
|
+
})(),
|
|
529
|
+
...PACKAGE_LOCK_FILES.map(async (filename) => {
|
|
530
|
+
try {
|
|
531
|
+
files.set(filename, await readFile(join(directory, filename)));
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') readable = false;
|
|
534
|
+
}
|
|
535
|
+
}),
|
|
536
|
+
]);
|
|
537
|
+
return {
|
|
538
|
+
files,
|
|
539
|
+
readable,
|
|
540
|
+
hasLockfile: PACKAGE_LOCK_FILES.some((filename) => files.has(filename)),
|
|
541
|
+
hasInstallableDependencies: ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some(
|
|
542
|
+
(field) => {
|
|
543
|
+
const dependencies = packageJSON?.[field];
|
|
544
|
+
return (
|
|
545
|
+
typeof dependencies === 'object' &&
|
|
546
|
+
dependencies !== null &&
|
|
547
|
+
!Array.isArray(dependencies) &&
|
|
548
|
+
Object.keys(dependencies).length > 0
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function installedPackageMetadataEqual(
|
|
556
|
+
previous: InstalledPackageMetadata,
|
|
557
|
+
current: InstalledPackageMetadata
|
|
558
|
+
): boolean {
|
|
559
|
+
if (!previous.readable || !current.readable || previous.files.size !== current.files.size) return false;
|
|
560
|
+
for (const [filename, contents] of previous.files) {
|
|
561
|
+
if (!current.files.get(filename)?.equals(contents)) return false;
|
|
562
|
+
}
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function installedRuntimeChanged(
|
|
567
|
+
previous: InstalledPackageMetadata,
|
|
568
|
+
current: InstalledPackageMetadata,
|
|
569
|
+
installationIsOpaque: boolean
|
|
570
|
+
): boolean {
|
|
571
|
+
return (
|
|
572
|
+
installationIsOpaque ||
|
|
573
|
+
(current.hasInstallableDependencies && !current.hasLockfile) ||
|
|
574
|
+
!installedPackageMetadataEqual(previous, current)
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function canonicalizeJSON(value: any): any {
|
|
579
|
+
if (Array.isArray(value)) return value.map(canonicalizeJSON);
|
|
580
|
+
if (!value || typeof value !== 'object') return value;
|
|
581
|
+
const canonical: Record<string, any> = Object.create(null);
|
|
582
|
+
for (const key of Object.keys(value).sort()) canonical[key] = canonicalizeJSON(value[key]);
|
|
583
|
+
return canonical;
|
|
584
|
+
}
|
|
585
|
+
|
|
490
586
|
/**
|
|
491
587
|
* Extract an application given payload (content of the application) or package (npm-compatible identifier to the application).
|
|
492
588
|
*
|
|
@@ -497,7 +593,10 @@ export const GIT_CREDENTIAL_HELPER_PATH = join(__dirname, 'gitCredentialHelper.j
|
|
|
497
593
|
* This method may be called from any Harper thread. Same-component calls are serialized across
|
|
498
594
|
* threads by the preparation lock below.
|
|
499
595
|
*/
|
|
500
|
-
export async function extractApplication(
|
|
596
|
+
export async function extractApplication(
|
|
597
|
+
application: Application,
|
|
598
|
+
deferCommit = false
|
|
599
|
+
): Promise<ExtractionTransaction | undefined> {
|
|
501
600
|
// Can't specify neither
|
|
502
601
|
if (!application.payload && !application.packageIdentifier) {
|
|
503
602
|
throw new Error('Either payload or package must be provided');
|
|
@@ -507,7 +606,6 @@ export async function extractApplication(application: Application) {
|
|
|
507
606
|
if (application.payload && application.packageIdentifier) {
|
|
508
607
|
throw new Error('Both payload and package cannot be provided');
|
|
509
608
|
}
|
|
510
|
-
|
|
511
609
|
// Resolve the tarball from the input
|
|
512
610
|
let tarballPath: string;
|
|
513
611
|
let tarball: Readable;
|
|
@@ -576,6 +674,7 @@ export async function extractApplication(application: Application) {
|
|
|
576
674
|
// not have, so this gates the pack step regardless of whether a credential happens to be in
|
|
577
675
|
// play.
|
|
578
676
|
const allowScripts = !!application.install?.allowInstallScripts;
|
|
677
|
+
if (allowScripts) application.installationIsOpaque = true;
|
|
579
678
|
// `--ignore-scripts` alone isn't a reliable way to enforce that: pacote's DirFetcher runs a
|
|
580
679
|
// git source's `prepare` unconditionally on npm versions before 11.0.0 (see
|
|
581
680
|
// packGitReferenceWithoutScripts), which is exactly what Node 22's bundled npm ships. For a
|
|
@@ -630,12 +729,13 @@ export async function extractApplication(application: Application) {
|
|
|
630
729
|
// component, and the per-component path means a sibling component never collides
|
|
631
730
|
// with (or sweeps) another's aside.
|
|
632
731
|
const asideStagingDir = join(dirname(application.dirPath), ASIDE_STAGING_DIR, basename(application.dirPath));
|
|
633
|
-
let
|
|
732
|
+
let asidePath: string | undefined;
|
|
634
733
|
try {
|
|
635
734
|
await access(application.dirPath, constants.F_OK);
|
|
636
735
|
await mkdir(asideStagingDir, { recursive: true });
|
|
637
|
-
|
|
638
|
-
|
|
736
|
+
const candidateAsidePath = join(asideStagingDir, `${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
737
|
+
await rename(application.dirPath, candidateAsidePath);
|
|
738
|
+
asidePath = candidateAsidePath;
|
|
639
739
|
} catch (err) {
|
|
640
740
|
// Ignore does not exist error
|
|
641
741
|
if (err.code !== 'ENOENT') {
|
|
@@ -644,35 +744,37 @@ export async function extractApplication(application: Application) {
|
|
|
644
744
|
}
|
|
645
745
|
// A directory existed for this component name prior to this deploy, so this is a redeploy of
|
|
646
746
|
// an already-active component rather than a first-time deploy. See `isNewComponent` above.
|
|
647
|
-
if (
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
747
|
+
if (asidePath) application.isNewComponent = false;
|
|
748
|
+
try {
|
|
749
|
+
await mkdir(application.dirPath, { recursive: true });
|
|
750
|
+
await pipeline(tarball, gunzip(), extract(application.dirPath));
|
|
751
|
+
|
|
752
|
+
const extracted = await readdir(application.dirPath, { withFileTypes: true });
|
|
753
|
+
if (extracted.length === 1 && extracted[0].isDirectory()) {
|
|
754
|
+
const topLevelDirPath = join(application.dirPath, extracted[0].name);
|
|
755
|
+
await mkdir(asideStagingDir, { recursive: true });
|
|
756
|
+
const tempDirPath = await mkdtemp(join(asideStagingDir, '.normalize-'));
|
|
757
|
+
await cp(topLevelDirPath, tempDirPath, { recursive: true });
|
|
758
|
+
await rm(topLevelDirPath, { recursive: true, force: true });
|
|
759
|
+
await cp(tempDirPath, application.dirPath, { recursive: true });
|
|
760
|
+
await rm(tempDirPath, { recursive: true, force: true });
|
|
761
|
+
}
|
|
762
|
+
} catch (error) {
|
|
763
|
+
try {
|
|
764
|
+
await rollbackExtractedDirectory(application, asideStagingDir, asidePath);
|
|
765
|
+
} catch (rollbackError) {
|
|
766
|
+
throw new AggregateError(
|
|
767
|
+
[error, rollbackError],
|
|
768
|
+
`Failed to extract ${application.name} and restore its previous component directory`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
throw error;
|
|
671
772
|
}
|
|
672
|
-
|
|
673
773
|
// Clean up the original tarball
|
|
674
774
|
if (shouldDeleteTarball && tarballPath) {
|
|
675
|
-
await rm(tarballPath, { force: true })
|
|
775
|
+
await rm(tarballPath, { force: true }).catch((error) =>
|
|
776
|
+
application.logger.warn(`Failed to remove temporary package ${tarballPath}:`, error)
|
|
777
|
+
);
|
|
676
778
|
}
|
|
677
779
|
|
|
678
780
|
// Remove this component's aside copies. The old worker may still hold files open
|
|
@@ -681,11 +783,76 @@ export async function extractApplication(application: Application) {
|
|
|
681
783
|
// earlier deploys whose workers have since exited, and a copy that survives because
|
|
682
784
|
// its worker is still live is swept by the next deploy. The failure is expected in
|
|
683
785
|
// the live-worker case, so it's logged at trace rather than as a warning.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
786
|
+
let settled = false;
|
|
787
|
+
const transaction: ExtractionTransaction = {
|
|
788
|
+
async commit() {
|
|
789
|
+
if (settled) return;
|
|
790
|
+
settled = true;
|
|
791
|
+
await cleanupExtractionStaging(application, asideStagingDir);
|
|
792
|
+
},
|
|
793
|
+
async rollback() {
|
|
794
|
+
if (settled) return;
|
|
795
|
+
settled = true;
|
|
796
|
+
await rollbackExtractedDirectory(application, asideStagingDir, asidePath);
|
|
797
|
+
},
|
|
798
|
+
};
|
|
799
|
+
if (deferCommit) return transaction;
|
|
800
|
+
await transaction.commit();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async function cleanupExtractionStaging(application: Application, asideStagingDir: string): Promise<void> {
|
|
804
|
+
try {
|
|
805
|
+
await rm(asideStagingDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
806
|
+
} catch (error) {
|
|
807
|
+
logger.trace?.(`Cleanup of previous ${application.name} component directory deferred: ${error.message}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function rollbackExtractedDirectory(
|
|
812
|
+
application: Application,
|
|
813
|
+
asideStagingDir: string,
|
|
814
|
+
asidePath: string | undefined
|
|
815
|
+
): Promise<void> {
|
|
816
|
+
await mkdir(asideStagingDir, { recursive: true });
|
|
817
|
+
const retryableRenameCodes = new Set(['EEXIST', 'ENOTEMPTY', 'EPERM', 'EACCES', 'EBUSY']);
|
|
818
|
+
const displaceCurrentDirectory = async () => {
|
|
819
|
+
const displacedPath = join(asideStagingDir, `.failed-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
820
|
+
let lastError: unknown;
|
|
821
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
822
|
+
try {
|
|
823
|
+
await rename(application.dirPath, displacedPath);
|
|
824
|
+
return;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
827
|
+
if (code === 'ENOENT') return;
|
|
828
|
+
if (!retryableRenameCodes.has(code ?? '')) throw error;
|
|
829
|
+
lastError = error;
|
|
830
|
+
await delay(10);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
throw lastError;
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
await displaceCurrentDirectory();
|
|
837
|
+
if (asidePath) {
|
|
838
|
+
let restored = false;
|
|
839
|
+
let restoreError: unknown;
|
|
840
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
841
|
+
try {
|
|
842
|
+
await rename(asidePath, application.dirPath);
|
|
843
|
+
restored = true;
|
|
844
|
+
break;
|
|
845
|
+
} catch (error) {
|
|
846
|
+
restoreError = error;
|
|
847
|
+
if (!retryableRenameCodes.has((error as NodeJS.ErrnoException).code ?? '')) break;
|
|
848
|
+
await displaceCurrentDirectory();
|
|
849
|
+
await delay(10);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (!restored) throw restoreError;
|
|
688
853
|
}
|
|
854
|
+
|
|
855
|
+
await cleanupExtractionStaging(application, asideStagingDir);
|
|
689
856
|
}
|
|
690
857
|
|
|
691
858
|
/**
|
|
@@ -711,7 +878,10 @@ export async function installApplication(application: Application) {
|
|
|
711
878
|
try {
|
|
712
879
|
// Does node_modules exist?
|
|
713
880
|
await access(join(application.dirPath, 'node_modules'), constants.F_OK);
|
|
714
|
-
application.logger.info(
|
|
881
|
+
application.logger.info(
|
|
882
|
+
`Application ${application.name} already has node_modules; skipping install and treating the runtime as opaque for redeploy comparison`
|
|
883
|
+
);
|
|
884
|
+
application.installationIsOpaque = true;
|
|
715
885
|
return;
|
|
716
886
|
} catch (err) {
|
|
717
887
|
if (err.code !== 'ENOENT') throw err;
|
|
@@ -735,6 +905,7 @@ export async function installApplication(application: Application) {
|
|
|
735
905
|
);
|
|
736
906
|
// if it succeeds, return
|
|
737
907
|
if (code === 0) {
|
|
908
|
+
application.installationIsOpaque = true;
|
|
738
909
|
return;
|
|
739
910
|
}
|
|
740
911
|
if (stdout) {
|
|
@@ -797,6 +968,7 @@ export async function installApplication(application: Application) {
|
|
|
797
968
|
|
|
798
969
|
// if it succeeds, return
|
|
799
970
|
if (code === 0) {
|
|
971
|
+
if (application.install?.allowInstallScripts) application.installationIsOpaque = true;
|
|
800
972
|
return;
|
|
801
973
|
}
|
|
802
974
|
|
|
@@ -854,6 +1026,7 @@ export async function installApplication(application: Application) {
|
|
|
854
1026
|
|
|
855
1027
|
// if it succeeds, return
|
|
856
1028
|
if (code === 0) {
|
|
1029
|
+
if (application.install?.allowInstallScripts) application.installationIsOpaque = true;
|
|
857
1030
|
return;
|
|
858
1031
|
}
|
|
859
1032
|
|
|
@@ -911,14 +1084,10 @@ export class Application {
|
|
|
911
1084
|
npmUserconfigPath?: string;
|
|
912
1085
|
#npmrcTempDir?: string;
|
|
913
1086
|
#gitCredentialSession?: GitCredentialSession;
|
|
914
|
-
//
|
|
915
|
-
// i.e. this deploy is the component's first, as opposed to a redeploy of something already
|
|
916
|
-
// active. Defaults true and is flipped to false by extractApplication when it finds (and
|
|
917
|
-
// renames aside) a pre-existing directory for this component name. Used by deployComponent to
|
|
918
|
-
// scope its unconditional requestRestart() call to genuinely new components (harper#1806):
|
|
919
|
-
// an existing, already-loaded component already has a live file watcher (Scope/EntryHandler)
|
|
920
|
-
// that independently requests a restart if the redeploy actually needs one.
|
|
1087
|
+
// Existing components rely on their runtime-equivalence checks; only a first deploy restarts unconditionally.
|
|
921
1088
|
isNewComponent: boolean = true;
|
|
1089
|
+
packageMetadataChanged: boolean = false;
|
|
1090
|
+
installationIsOpaque: boolean = false;
|
|
922
1091
|
|
|
923
1092
|
constructor({ name, payload, packageIdentifier, install, onInstallLine, credentials }: ApplicationOptions) {
|
|
924
1093
|
this.name = name;
|
|
@@ -1064,12 +1233,14 @@ export function derivePackageIdentifier(packageIdentifier: string) {
|
|
|
1064
1233
|
* @returns A promise that resolves when all preparation steps complete.
|
|
1065
1234
|
*/
|
|
1066
1235
|
export async function prepareApplication(application: Application) {
|
|
1067
|
-
await broadcastDeployStart(application.name);
|
|
1236
|
+
const deploymentId = await broadcastDeployStart(application.name);
|
|
1068
1237
|
try {
|
|
1069
1238
|
const commandTimeoutMs = application.install?.timeout ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
1070
1239
|
await withComponentPreparationLock(
|
|
1071
1240
|
application.dirPath,
|
|
1072
1241
|
async () => {
|
|
1242
|
+
const previousPackageMetadata = await readInstalledPackageMetadata(application.dirPath);
|
|
1243
|
+
let extraction: ExtractionTransaction | undefined;
|
|
1073
1244
|
try {
|
|
1074
1245
|
// Materialize the per-deploy `.npmrc` before extraction so both `npm pack` (extract) and
|
|
1075
1246
|
// `npm install` authenticate against the private registry; always remove it afterward.
|
|
@@ -1080,11 +1251,30 @@ export async function prepareApplication(application: Application) {
|
|
|
1080
1251
|
// already gone by the time the component's dependency tree (and any install script it is
|
|
1081
1252
|
// allowed to run) executes.
|
|
1082
1253
|
await application.startGitCredentialSession();
|
|
1083
|
-
await extractApplication(application);
|
|
1254
|
+
extraction = await extractApplication(application, true);
|
|
1084
1255
|
} finally {
|
|
1085
1256
|
await application.cleanupGitCredentialSession();
|
|
1086
1257
|
}
|
|
1087
1258
|
await installApplication(application);
|
|
1259
|
+
if (!application.isNewComponent) {
|
|
1260
|
+
const currentPackageMetadata = await readInstalledPackageMetadata(application.dirPath);
|
|
1261
|
+
application.packageMetadataChanged = installedRuntimeChanged(
|
|
1262
|
+
previousPackageMetadata,
|
|
1263
|
+
currentPackageMetadata,
|
|
1264
|
+
application.installationIsOpaque
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
await extraction?.commit();
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
try {
|
|
1270
|
+
await extraction?.rollback();
|
|
1271
|
+
} catch (rollbackError) {
|
|
1272
|
+
throw new AggregateError(
|
|
1273
|
+
[error, rollbackError],
|
|
1274
|
+
`Failed to prepare ${application.name} and restore its previous component directory`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
throw error;
|
|
1088
1278
|
} finally {
|
|
1089
1279
|
await application.cleanupTransientNpmrc();
|
|
1090
1280
|
}
|
|
@@ -1111,7 +1301,7 @@ export async function prepareApplication(application: Application) {
|
|
|
1111
1301
|
}
|
|
1112
1302
|
);
|
|
1113
1303
|
} finally {
|
|
1114
|
-
broadcastDeployEnd(application.name);
|
|
1304
|
+
broadcastDeployEnd(application.name, deploymentId);
|
|
1115
1305
|
}
|
|
1116
1306
|
}
|
|
1117
1307
|
|
|
@@ -4,6 +4,8 @@ import { forComponent } from '../utility/logging/harper_logger.ts';
|
|
|
4
4
|
import { scopedImport } from '../security/jsLoader.ts';
|
|
5
5
|
import * as env from '../utility/environment/environmentManager.ts';
|
|
6
6
|
import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
|
|
7
|
+
import { RuntimeModuleTracker } from './RuntimeModuleTracker.ts';
|
|
8
|
+
import { deployLifecycle } from './deployLifecycle.ts';
|
|
7
9
|
|
|
8
10
|
export class MissingDefaultFilesOptionError extends Error {
|
|
9
11
|
constructor() {
|
|
@@ -24,14 +26,18 @@ export class ApplicationScope {
|
|
|
24
26
|
mode?: 'native' | 'vm' | 'vm-current-context' | 'compartment'; // option to set this from the scope
|
|
25
27
|
dependencyLoader?: 'native' | 'app' | 'auto'; // option to set this from the scope
|
|
26
28
|
allowedPath?: string;
|
|
29
|
+
runtimeRoot?: string;
|
|
27
30
|
config: any;
|
|
28
31
|
moduleCache: any; // used by the loader to retain a cache of modules, type is an internal detail of the loader
|
|
32
|
+
#runtimeModules: RuntimeModuleTracker;
|
|
29
33
|
constructor(name: string, resources: Resources, server: Server, isInternal = false) {
|
|
30
34
|
this.name = name;
|
|
31
35
|
this.logger = forComponent(name, !isInternal);
|
|
32
36
|
|
|
33
37
|
this.resources = resources;
|
|
34
38
|
this.server = server;
|
|
39
|
+
this.#runtimeModules = new RuntimeModuleTracker(() => this.runtimeRoot);
|
|
40
|
+
if (deployLifecycle.isDeployInFlight(name)) this.#runtimeModules.beginDeploy();
|
|
35
41
|
|
|
36
42
|
this.mode = env.get(CONFIG_PARAMS.APPLICATIONS_MODULELOADER) ?? 'vm-current-context';
|
|
37
43
|
this.dependencyLoader = env.get(CONFIG_PARAMS.APPLICATIONS_DEPENDENCYLOADER);
|
|
@@ -50,4 +56,24 @@ export class ApplicationScope {
|
|
|
50
56
|
async import(filePath: string): Promise<unknown> {
|
|
51
57
|
return scopedImport(filePath, this);
|
|
52
58
|
}
|
|
59
|
+
|
|
60
|
+
recordLoadedModule(moduleUrl: string, source: string | Buffer): void {
|
|
61
|
+
this.#runtimeModules.recordModule(moduleUrl, source);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
recordModuleResolution(specifier: string, referrer: string, resolvedUrl: string): void {
|
|
65
|
+
this.#runtimeModules.recordResolution(specifier, referrer, resolvedUrl);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
markNativeRuntime(): void {
|
|
69
|
+
this.#runtimeModules.markNativeRuntime();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
beginDeploy(): void {
|
|
73
|
+
this.#runtimeModules.beginDeploy();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
finishDeploy(): Promise<boolean> {
|
|
77
|
+
return this.#runtimeModules.finishDeploy();
|
|
78
|
+
}
|
|
53
79
|
}
|