@use-the-fork/spindle 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/README.md +3 -2
- package/build/Assets/Blueprints/Catalog.js +110 -0
- package/build/Assets/Blueprints/agents/general.agent.md +67 -0
- package/build/Assets/Blueprints/agents/sdd-code-reviewer.agent.md +62 -0
- package/build/Assets/Blueprints/agents/sdd-code-slop-reviewer.agent.md +59 -0
- package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +77 -0
- package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +42 -0
- package/build/Assets/Blueprints/agents/sdd-re-review.agent.md +57 -0
- package/build/Assets/Blueprints/agents/sdd-reviewer.agent.md +89 -0
- package/build/Assets/Blueprints/agents/sdd-task-reviewer.agent.md +54 -0
- package/build/Assets/Blueprints/agents/shared-codebase-analyzer.agent.md +151 -0
- package/build/Assets/Blueprints/agents/shared-codebase-locator.agent.md +130 -0
- package/build/Assets/Blueprints/agents/shared-codebase-pattern-finder.agent.md +247 -0
- package/build/Assets/Blueprints/agents/shared-explore.agent.md +53 -0
- package/build/Assets/Blueprints/agents/shared-r.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rw.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rwx.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-web-search-researcher.agent.md +122 -0
- package/build/Assets/Blueprints/instructions/code-commenting.instructions.md +39 -0
- package/build/Assets/Blueprints/instructions/source-architecture.instructions.md +15 -0
- package/build/Assets/Blueprints/skills/asd-ste100/CREDITS.MD +1 -0
- package/build/Assets/Blueprints/skills/asd-ste100/SKILL.md +83 -0
- package/build/Assets/Blueprints/skills/asd-ste100/examples/before-after.md +107 -0
- package/build/Assets/Blueprints/skills/asd-ste100/references/writing-rules.md +65 -0
- package/build/Assets/Blueprints/skills/author-agent/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/author-design/SKILL.md +38 -0
- package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +28 -0
- package/build/Assets/Blueprints/skills/author-skill/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/grill/SKILL.md +33 -0
- package/build/Assets/Blueprints/skills/grill-design/SKILL.md +41 -0
- package/build/Assets/Blueprints/skills/grill-with-docs/SKILL.md +14 -0
- package/build/Assets/Blueprints/skills/implement/SKILL.md +79 -0
- package/build/Assets/Blueprints/skills/implement-spec/SKILL.md +34 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/HTML-REPORT.md +129 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/SKILL.md +74 -0
- package/build/Assets/Blueprints/skills/int-code-review/SKILL.md +76 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/SKILL.md +238 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_sections.md +36 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_template.md +42 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-closing-brace-labels.md +112 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-empty-docblocks.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-narration.md +118 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-placeholder.md +130 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-generic-catch.md +153 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-missing-real.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-generic-placeholders.md +120 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-over-descriptive.md +108 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-suffix-abuse.md +167 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-type-in-name.md +100 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-dependency-creep.md +114 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md +136 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md +161 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-useless-wrapper.md +140 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-as-any-escape.md +127 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-debug-artifacts.md +144 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-hyper-consistent.md +84 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-no-hack-scars.md +113 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md +191 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-doesnt-throw.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mirror-implementation.md +125 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mock-everything.md +148 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-snapshot-abuse.md +131 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/SKILL.md +116 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DEEPENING.md +37 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/SKILL.md +78 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/ADR-FORMAT.md +47 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/GLOSSARY-FORMAT.md +60 -0
- package/build/Assets/Blueprints/skills/int-prototype/LOGIC.md +67 -0
- package/build/Assets/Blueprints/skills/int-prototype/SKILL.md +30 -0
- package/build/Assets/Blueprints/skills/int-prototype/UI.md +112 -0
- package/build/Assets/Blueprints/skills/int-retrospective/SKILL.md +88 -0
- package/build/Assets/Blueprints/skills/int-retrospective/assets/retrospective-prompt.md +38 -0
- package/build/Assets/Blueprints/skills/int-retrospective/references/assessment.md +56 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +64 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +40 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/reviewer-prompt.md +43 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/aggregate-review.md +7 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +25 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/implementer-corrections.md +9 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +26 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/verification-evidence.md +7 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/SKILL.md +51 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/mocking.md +31 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/tests.md +68 -0
- package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/int-verification-before-completion/SKILL.md +25 -0
- package/build/Assets/Blueprints/skills/int-writing-agents/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/int-writing-for-agents/SKILL.md +86 -0
- package/build/Assets/Blueprints/skills/int-writing-instruction/SKILL.md +57 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +129 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/interactive.md +83 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/reference.md +32 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/arguments.md +50 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/interactive-skills.md +53 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/persuasion-principles.md +220 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/reference-skills.md +20 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +48 -0
- package/build/Assets/Blueprints/skills/retrospective/SKILL.md +16 -0
- package/build/Assets/Blueprints/skills/setup-project/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/to-spec/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/to-tickets/SKILL.md +49 -0
- package/build/Assets/Blueprints/skills/update-tooling/SKILL.md +86 -0
- package/build/Assets/Templates/Catalog.js +12 -0
- package/build/Assets/Templates/Catalog.ts +17 -0
- package/build/Assets/Templates/project/copilot-instructions.md +17 -0
- package/build/Assets/Templates/project/domain.md +51 -0
- package/build/Assets/Templates/project/ticket-tracker.md +33 -0
- package/build/Assets/Templates/sdd/design.md +25 -0
- package/build/Assets/Templates/sdd/spec.md +29 -0
- package/build/Assets/Templates/sdd/ticket.md +15 -0
- package/build/BlueprintManagement/BlueprintManagementServiceProvider.js +15 -0
- package/build/BlueprintManagement/Catalog/BlueprintCatalog.js +238 -0
- package/build/BlueprintManagement/Commands/BlueprintCommandOptions.js +9 -0
- package/build/BlueprintManagement/Commands/ConflictPolicy.js +6 -0
- package/build/BlueprintManagement/Commands/InitCommand.js +117 -0
- package/build/BlueprintManagement/Commands/Prompts.js +66 -0
- package/build/BlueprintManagement/Commands/UpdateCommand.js +86 -0
- package/build/BlueprintManagement/Handlers/Agents/AgentHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/BlueprintHandlers.js +4 -0
- package/build/BlueprintManagement/Handlers/Instructions/InstructionHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/Skills/SkillHandler.js +10 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintCatalogPlanner.js +87 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintRemovalCoordinator.js +78 -0
- package/build/BlueprintManagement/Services/BlueprintReconciliationService.js +391 -0
- package/build/BlueprintManagement/Services/InstallationService.js +54 -0
- package/build/BlueprintManagement/Support/BlueprintFiles.js +113 -0
- package/build/BlueprintManagement/Types/Blueprint.js +1 -0
- package/build/BlueprintManagement/Types/Installation.js +1 -0
- package/build/BlueprintManagement/Types/Reconciliation.js +1 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersion.js +70 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersioning.js +55 -0
- package/build/Commands/Command.js +3 -0
- package/build/Commands/CommandOptions.js +12 -0
- package/build/Config/Config.js +3 -0
- package/build/Foundation/Application.js +112 -0
- package/build/Foundation/ApplicationBuilder.js +23 -0
- package/build/Foundation/Bootstrap/Bootstrapper.js +2 -0
- package/build/Foundation/Bootstrap/DiscoverGitRoot.js +8 -0
- package/build/Foundation/Bootstrap/LoadConsoleArgs.js +47 -0
- package/build/Foundation/Bootstrap/LoadProjectConfiguration.js +144 -0
- package/build/Foundation/Bootstrap/RegisterProviders.js +12 -0
- package/build/Foundation/Bootstrap/RequireProjectConfiguration.js +14 -0
- package/build/Foundation/ConsoleInvocation.js +1 -0
- package/build/Foundation/Container.js +185 -0
- package/build/Foundation/FoundationServiceProvider.js +5 -0
- package/build/Foundation/Kernel.js +30 -0
- package/build/Foundation/Service.js +23 -0
- package/build/Foundation/ServiceProvider.js +12 -0
- package/build/Git/GitServiceProvider.js +8 -0
- package/build/Git/Services/GitService.js +26 -0
- package/build/Project/ProjectServiceProvider.js +8 -0
- package/build/Project/Services/ProjectService.js +124 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +35 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ClaimCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +37 -0
- package/build/SubagentDrivenDevelopment/Commands/CreateCommand.js +26 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingCreateCommand.js +39 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingUpdateCommand.js +36 -0
- package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/NextCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/ReadyCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/RequirementsDecideCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ResumeCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +55 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewRequireCommand.js +12 -0
- package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/SubmitCommand.js +59 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCommentCommand.js +24 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCreateCommand.js +25 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketUpdateCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/groups.js +36 -0
- package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +449 -0
- package/build/SubagentDrivenDevelopment/Evidence/ReportEvidence.js +80 -0
- package/build/SubagentDrivenDevelopment/Models/Assignments.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Evidence.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Findings.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Options.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Workflow.js +16 -0
- package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +155 -0
- package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +71 -0
- package/build/SubagentDrivenDevelopment/Persistence/FeatureWorkspace.js +76 -0
- package/build/SubagentDrivenDevelopment/Persistence/FileSystem.js +28 -0
- package/build/SubagentDrivenDevelopment/Persistence/FindingSchema.js +81 -0
- package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +322 -0
- package/build/SubagentDrivenDevelopment/Persistence/PathSafety.js +62 -0
- package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +95 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReadinessReader.js +40 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +68 -0
- package/build/SubagentDrivenDevelopment/Persistence/TicketRepository.js +147 -0
- package/build/SubagentDrivenDevelopment/Persistence/WorkspaceTransaction.js +282 -0
- package/build/SubagentDrivenDevelopment/Policies/AcceptancePolicy.js +100 -0
- package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +93 -0
- package/build/SubagentDrivenDevelopment/Policies/FindingPolicy.js +53 -0
- package/build/SubagentDrivenDevelopment/Policies/InputValidation.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +26 -0
- package/build/SubagentDrivenDevelopment/Policies/RequiredReviewers.js +12 -0
- package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +87 -0
- package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +85 -0
- package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +269 -0
- package/build/SubagentDrivenDevelopment/Services/ChecksService.js +319 -0
- package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +253 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryFindingService.js +302 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +160 -0
- package/build/SubagentDrivenDevelopment/Services/DeliverySubmissionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +138 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +399 -0
- package/build/SubagentDrivenDevelopment/Services/FindingService.js +392 -0
- package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +258 -0
- package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +21 -0
- package/build/SubagentDrivenDevelopment/Services/RequirementsService.js +216 -0
- package/build/SubagentDrivenDevelopment/Services/ReviewService.js +471 -0
- package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +299 -0
- package/build/SubagentDrivenDevelopment/Services/TicketService.js +186 -0
- package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +113 -0
- package/build/Support/Records.js +5 -0
- package/build/Support/Repository.js +139 -0
- package/build/Support/Stacks.js +10 -0
- package/build/TemplateRendering/Computed/ComputedContextService.js +13 -0
- package/build/TemplateRendering/Computed/ComputedVariableProvider.js +6 -0
- package/build/TemplateRendering/Computed/ComputedVariableProviders.js +6 -0
- package/build/TemplateRendering/Computed/Providers/InternalSkillDescriptionProvider.js +6 -0
- package/build/TemplateRendering/Computed/Providers/SpindlePackageManagerProvider.js +11 -0
- package/build/TemplateRendering/Computed/ResolveRenderContext.js +18 -0
- package/build/TemplateRendering/Rendering/RenderText.js +43 -0
- package/build/TemplateRendering/TemplateRenderingServiceProvider.js +8 -0
- package/build/Tooling/Commands/ToolingAddCommand.js +29 -0
- package/build/Tooling/Commands/ToolingListCommand.js +19 -0
- package/build/Tooling/Commands/ToolingRemoveCommand.js +19 -0
- package/build/Tooling/Commands/ToolingUpdateCommand.js +30 -0
- package/build/Tooling/Commands/groups.js +4 -0
- package/build/Tooling/Models/ToolingEntry.js +1 -0
- package/build/Tooling/Services/ToolingService.js +278 -0
- package/build/Tooling/ToolingServiceProvider.js +15 -0
- package/build/cli.js +36 -0
- package/build/index.js +31 -0
- package/package.json +54 -3
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
import { compareSemver, parseSemver, readBlueprintVersion, } from "./BlueprintVersion.js";
|
|
4
|
+
export function validateVersionedMetadata(kind, entryFile, relativePath, renderedText) {
|
|
5
|
+
if (relativePath !== entryFile)
|
|
6
|
+
return;
|
|
7
|
+
const version = matter(renderedText).data.metadata?.spindle?.version;
|
|
8
|
+
if (typeof version !== "string" || parseSemver(version) === null) {
|
|
9
|
+
throw new Error(`Invalid ${kind} metadata in '${relativePath}': expected a semver \`metadata.spindle.version\` in frontmatter.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function planVersionedUpdate(input, kind, entryFile, installedFile, malformedInstalledMetadata) {
|
|
13
|
+
const { name, sourceFiles, target, installed, resolveContext } = input;
|
|
14
|
+
const source = sourceFiles.find((file) => file.outputPath === entryFile)?.sourcePath;
|
|
15
|
+
const context = source === undefined ? undefined : await resolveContext();
|
|
16
|
+
if (source !== undefined && context === undefined) {
|
|
17
|
+
throw new Error(`Cannot render the shipped ${kind.toLowerCase()} Blueprint '${name}' without resolved project Variables.`);
|
|
18
|
+
}
|
|
19
|
+
let renderedEntry;
|
|
20
|
+
const shipped = source === undefined
|
|
21
|
+
? null
|
|
22
|
+
: await readBlueprintVersion(source, context, {
|
|
23
|
+
onRendered: (sourceText, renderedText) => {
|
|
24
|
+
renderedEntry = { sourcePath: source, outputPath: entryFile, sourceText, renderedText };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const shippedVersion = shipped === null ? null : parseSemver(shipped);
|
|
28
|
+
if (shipped === null || shippedVersion === null) {
|
|
29
|
+
throw new Error(`The shipped ${kind.toLowerCase()} Blueprint '${name}' has no valid semver version in its ${entryFile}.`);
|
|
30
|
+
}
|
|
31
|
+
if (!installed)
|
|
32
|
+
return { status: "new", renderedEntry };
|
|
33
|
+
const installedPath = installedFile(target);
|
|
34
|
+
const installedText = await readBlueprintVersion(installedPath, undefined, {
|
|
35
|
+
rejectMalformed: malformedInstalledMetadata === "error",
|
|
36
|
+
});
|
|
37
|
+
const installedVersion = installedText === null ? null : parseSemver(installedText);
|
|
38
|
+
if (installedText === null || installedVersion === null) {
|
|
39
|
+
const description = installedText === null ? "no version" : `non-semver version '${installedText}'`;
|
|
40
|
+
return {
|
|
41
|
+
status: "warning",
|
|
42
|
+
warning: `Skipping ${kind} '${name}': installed ${basename(installedPath)} has ${description}.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (compareSemver(shippedVersion, installedVersion) > 0) {
|
|
46
|
+
return {
|
|
47
|
+
status: "update",
|
|
48
|
+
installedVersion: installedText,
|
|
49
|
+
availableVersion: shipped,
|
|
50
|
+
versionedEntry: source === undefined ? undefined : { sourcePath: source, outputPath: entryFile },
|
|
51
|
+
renderedEntry,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { status: "up-to-date" };
|
|
55
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { Config } from "../Config/Config.js";
|
|
4
|
+
import { ApplicationBuilder } from "./ApplicationBuilder.js";
|
|
5
|
+
import { Container } from "./Container.js";
|
|
6
|
+
import { FoundationServiceProvider } from "./FoundationServiceProvider.js";
|
|
7
|
+
import { GitServiceProvider } from "../Git/GitServiceProvider.js";
|
|
8
|
+
export class Application extends Container {
|
|
9
|
+
config = new Config();
|
|
10
|
+
_basePath;
|
|
11
|
+
providers = new Map();
|
|
12
|
+
commands = new Set();
|
|
13
|
+
selectedBootstrapProviders;
|
|
14
|
+
bootableProviders = [];
|
|
15
|
+
bootstrapPromise;
|
|
16
|
+
_hasBeenBootstrapped = false;
|
|
17
|
+
bootPromise;
|
|
18
|
+
_hasBeenBooted = false;
|
|
19
|
+
constructor({ basePath }) {
|
|
20
|
+
super();
|
|
21
|
+
this._basePath = resolve(basePath);
|
|
22
|
+
this.instance("path.base", this._basePath);
|
|
23
|
+
this.registerBaseBindings();
|
|
24
|
+
this.registerBaseServiceProviders();
|
|
25
|
+
}
|
|
26
|
+
get owner() {
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
static configure(basePath, providers = []) {
|
|
30
|
+
return new ApplicationBuilder(new Application({ basePath }))
|
|
31
|
+
.withKernels()
|
|
32
|
+
.withProviders(providers);
|
|
33
|
+
}
|
|
34
|
+
joinPaths(basePath, path = "") {
|
|
35
|
+
if (path === "")
|
|
36
|
+
return basePath;
|
|
37
|
+
return isAbsolute(path) ? path : join(basePath, path);
|
|
38
|
+
}
|
|
39
|
+
workspacePath(path = "") {
|
|
40
|
+
return this.joinPaths(this.workspaceRoot, path);
|
|
41
|
+
}
|
|
42
|
+
appPath(path = "") {
|
|
43
|
+
return this.joinPaths(fileURLToPath(new URL("../", import.meta.url)), path);
|
|
44
|
+
}
|
|
45
|
+
basePath(path = "") {
|
|
46
|
+
return this.joinPaths(this._basePath, path);
|
|
47
|
+
}
|
|
48
|
+
get workspaceRoot() {
|
|
49
|
+
return this.stringValue("path.workspace");
|
|
50
|
+
}
|
|
51
|
+
get hasBeenBootstrapped() {
|
|
52
|
+
return this._hasBeenBootstrapped;
|
|
53
|
+
}
|
|
54
|
+
get hasBeenBooted() {
|
|
55
|
+
return this._hasBeenBooted;
|
|
56
|
+
}
|
|
57
|
+
selectBootstrapProviders(providers) {
|
|
58
|
+
this.selectedBootstrapProviders = [...new Set(providers)];
|
|
59
|
+
}
|
|
60
|
+
bootstrapProviders(fallback) {
|
|
61
|
+
return this.selectedBootstrapProviders ?? fallback;
|
|
62
|
+
}
|
|
63
|
+
register(Provider) {
|
|
64
|
+
if (this.providers.has(Provider))
|
|
65
|
+
return;
|
|
66
|
+
const provider = new Provider(this);
|
|
67
|
+
this.providers.set(Provider, provider);
|
|
68
|
+
for (const Command of Provider.commands()) {
|
|
69
|
+
if (!this.commands.has(Command)) {
|
|
70
|
+
this.singleton(Command);
|
|
71
|
+
this.commands.add(Command);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
provider.register();
|
|
75
|
+
this.bootableProviders.push(provider);
|
|
76
|
+
}
|
|
77
|
+
async bootstrapWith(bootstrappers) {
|
|
78
|
+
if (!this.bootstrapPromise) {
|
|
79
|
+
const constructors = [...bootstrappers];
|
|
80
|
+
this.bootstrapPromise = Promise.resolve().then(async () => {
|
|
81
|
+
for (const Bootstrapper of constructors)
|
|
82
|
+
await new Bootstrapper().bootstrap(this);
|
|
83
|
+
this._hasBeenBootstrapped = true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
await this.bootstrapPromise;
|
|
87
|
+
}
|
|
88
|
+
async boot() {
|
|
89
|
+
this.bootPromise ??= Promise.resolve().then(async () => {
|
|
90
|
+
for (const provider of this.bootableProviders)
|
|
91
|
+
await provider.boot();
|
|
92
|
+
this._hasBeenBooted = true;
|
|
93
|
+
});
|
|
94
|
+
await this.bootPromise;
|
|
95
|
+
}
|
|
96
|
+
registerBaseBindings() {
|
|
97
|
+
this.instance("app", this);
|
|
98
|
+
this.instance(Application, this);
|
|
99
|
+
this.instance(Container, this);
|
|
100
|
+
}
|
|
101
|
+
registerBaseServiceProviders() {
|
|
102
|
+
this.register(FoundationServiceProvider);
|
|
103
|
+
this.register(GitServiceProvider);
|
|
104
|
+
}
|
|
105
|
+
stringValue(key) {
|
|
106
|
+
const value = this.value(key);
|
|
107
|
+
if (typeof value !== "string") {
|
|
108
|
+
throw new Error(`Application path '${key}' must be a string.`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RegisterProviders } from "./Bootstrap/RegisterProviders.js";
|
|
2
|
+
import { Kernel } from "./Kernel.js";
|
|
3
|
+
export class ApplicationBuilder {
|
|
4
|
+
application;
|
|
5
|
+
kernelsRegistered = false;
|
|
6
|
+
constructor(application) {
|
|
7
|
+
this.application = application;
|
|
8
|
+
}
|
|
9
|
+
withKernels() {
|
|
10
|
+
if (!this.kernelsRegistered) {
|
|
11
|
+
this.application.singleton(Kernel, (application) => new Kernel(application));
|
|
12
|
+
this.kernelsRegistered = true;
|
|
13
|
+
}
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
withProviders(providers = []) {
|
|
17
|
+
RegisterProviders.merge(providers);
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
create() {
|
|
21
|
+
return this.application;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Bootstrapper } from "./Bootstrapper.js";
|
|
2
|
+
import { GitService } from "../../Git/Services/GitService.js";
|
|
3
|
+
export class DiscoverGitRoot extends Bootstrapper {
|
|
4
|
+
async bootstrap(application) {
|
|
5
|
+
const git = await application.make(GitService);
|
|
6
|
+
application.instance("path.workspace", git.discoverRoot(application.basePath()));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Bootstrapper } from "./Bootstrapper.js";
|
|
2
|
+
import { Repository } from "../../Support/Repository.js";
|
|
3
|
+
function isStringArray(value) {
|
|
4
|
+
if (!Array.isArray(value))
|
|
5
|
+
return false;
|
|
6
|
+
for (const item of value) {
|
|
7
|
+
if (typeof item !== "string")
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
function isOptions(value) {
|
|
13
|
+
return (typeof value === "object" &&
|
|
14
|
+
value !== null &&
|
|
15
|
+
!Array.isArray(value) &&
|
|
16
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null));
|
|
17
|
+
}
|
|
18
|
+
function isConsoleInvocation(value) {
|
|
19
|
+
return (typeof value === "object" &&
|
|
20
|
+
value !== null &&
|
|
21
|
+
"raw" in value &&
|
|
22
|
+
"script" in value &&
|
|
23
|
+
"commandPath" in value &&
|
|
24
|
+
"options" in value &&
|
|
25
|
+
"positionals" in value &&
|
|
26
|
+
isStringArray(value.raw) &&
|
|
27
|
+
typeof value.script === "string" &&
|
|
28
|
+
isStringArray(value.commandPath) &&
|
|
29
|
+
isOptions(value.options) &&
|
|
30
|
+
isStringArray(value.positionals));
|
|
31
|
+
}
|
|
32
|
+
export class LoadConsoleArgs extends Bootstrapper {
|
|
33
|
+
bootstrap(application) {
|
|
34
|
+
const value = application.value("console.invocation");
|
|
35
|
+
if (!isConsoleInvocation(value)) {
|
|
36
|
+
throw new Error("Console invocation must include valid raw, script, command, options, and positional data.");
|
|
37
|
+
}
|
|
38
|
+
const invocation = value;
|
|
39
|
+
const args = new Repository();
|
|
40
|
+
args.set("raw", invocation.raw);
|
|
41
|
+
args.set("script", invocation.script);
|
|
42
|
+
args.set("commandPath", invocation.commandPath);
|
|
43
|
+
args.set("options", invocation.options);
|
|
44
|
+
args.set("positionals", invocation.positionals);
|
|
45
|
+
application.instance("console.args", args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Bootstrapper } from "./Bootstrapper.js";
|
|
3
|
+
import { Records } from "../../Support/Records.js";
|
|
4
|
+
import { Stacks } from "../../Support/Stacks.js";
|
|
5
|
+
const malformed = '.github/spindle.json is malformed; expected {"stacks": [...], "spindle_package_manager"?: string, "tooling"?: [...]}. Fix the file or run `spindle init`.';
|
|
6
|
+
export class LoadProjectConfiguration extends Bootstrapper {
|
|
7
|
+
static parse(raw) {
|
|
8
|
+
let data;
|
|
9
|
+
try {
|
|
10
|
+
data = JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (!(error instanceof Error))
|
|
14
|
+
throw error;
|
|
15
|
+
return {
|
|
16
|
+
status: "invalid",
|
|
17
|
+
data: raw,
|
|
18
|
+
error,
|
|
19
|
+
reason: ".github/spindle.json is not valid JSON.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!this.isConfiguration(data)) {
|
|
23
|
+
return { status: "invalid", data, error: new Error(malformed), reason: malformed };
|
|
24
|
+
}
|
|
25
|
+
if (data.stacks.length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
Stacks.validate(data.stacks);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (!(error instanceof Error))
|
|
31
|
+
throw error;
|
|
32
|
+
return { status: "invalid", data, error, reason: error.message };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { status: "valid", data };
|
|
36
|
+
}
|
|
37
|
+
static async load(path) {
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = await readFile(path, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (Records.isRecord(error) && error.code === "ENOENT")
|
|
44
|
+
return { status: "absent" };
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
return this.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
static current(application) {
|
|
50
|
+
const state = application.value("config.project");
|
|
51
|
+
if (Records.isRecord(state)) {
|
|
52
|
+
if (state.status === "absent")
|
|
53
|
+
return { status: "absent" };
|
|
54
|
+
if (state.status === "valid" && this.isConfiguration(state.data)) {
|
|
55
|
+
return { status: "valid", data: state.data };
|
|
56
|
+
}
|
|
57
|
+
if (state.status === "invalid" &&
|
|
58
|
+
state.error instanceof Error &&
|
|
59
|
+
typeof state.reason === "string") {
|
|
60
|
+
return { status: "invalid", data: state.data, error: state.error, reason: state.reason };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error("Application binding 'config.project' must contain a project configuration state.");
|
|
64
|
+
}
|
|
65
|
+
static async withTemporaryState(application, state, operation) {
|
|
66
|
+
this.current(application);
|
|
67
|
+
const current = application.value("config.project");
|
|
68
|
+
if (!Records.isRecord(current)) {
|
|
69
|
+
throw new Error("Application binding 'config.project' must contain a project configuration state.");
|
|
70
|
+
}
|
|
71
|
+
const previous = { ...current };
|
|
72
|
+
const replace = (replacement) => {
|
|
73
|
+
for (const key of Object.keys(current))
|
|
74
|
+
delete current[key];
|
|
75
|
+
Object.assign(current, replacement);
|
|
76
|
+
};
|
|
77
|
+
replace(state);
|
|
78
|
+
try {
|
|
79
|
+
return await operation();
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
replace(previous);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
static require(state) {
|
|
86
|
+
if (state.status === "absent") {
|
|
87
|
+
throw new Error("This repository has no .github/spindle.json. Run `spindle init` to create .github/spindle.json.");
|
|
88
|
+
}
|
|
89
|
+
if (state.status === "invalid") {
|
|
90
|
+
if (state.reason.startsWith("Invalid stack '"))
|
|
91
|
+
throw state.error;
|
|
92
|
+
throw new Error(`${state.reason} Run \`spindle init\` to create .github/spindle.json.`);
|
|
93
|
+
}
|
|
94
|
+
return { config: state.data, stacks: state.data.stacks };
|
|
95
|
+
}
|
|
96
|
+
static requireCurrent(application) {
|
|
97
|
+
return this.require(this.current(application));
|
|
98
|
+
}
|
|
99
|
+
static async refresh(application) {
|
|
100
|
+
const state = await this.load(application.workspacePath(".github/spindle.json"));
|
|
101
|
+
if (application.bound("config.project")) {
|
|
102
|
+
const current = application.value("config.project");
|
|
103
|
+
if (!Records.isRecord(current)) {
|
|
104
|
+
throw new Error("Application binding 'config.project' must contain a project configuration state.");
|
|
105
|
+
}
|
|
106
|
+
for (const key of Object.keys(current))
|
|
107
|
+
delete current[key];
|
|
108
|
+
Object.assign(current, state);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
application.instance("config.project", state);
|
|
112
|
+
}
|
|
113
|
+
return state;
|
|
114
|
+
}
|
|
115
|
+
async bootstrap(application) {
|
|
116
|
+
await LoadProjectConfiguration.refresh(application);
|
|
117
|
+
}
|
|
118
|
+
static isConfiguration(value) {
|
|
119
|
+
return (Records.isRecord(value) &&
|
|
120
|
+
Array.isArray(value.stacks) &&
|
|
121
|
+
value.stacks.every((stack) => typeof stack === "string") &&
|
|
122
|
+
(value.spindle_package_manager === undefined ||
|
|
123
|
+
(typeof value.spindle_package_manager === "string" &&
|
|
124
|
+
this.isSingleLine(value.spindle_package_manager))) &&
|
|
125
|
+
(value.tooling === undefined ||
|
|
126
|
+
(Array.isArray(value.tooling) &&
|
|
127
|
+
value.tooling.every((entry) => this.isToolingEntry(entry)) &&
|
|
128
|
+
new Set(value.tooling.map((entry) => entry.key)).size === value.tooling.length)));
|
|
129
|
+
}
|
|
130
|
+
static isToolingEntry(value) {
|
|
131
|
+
return (Records.isRecord(value) &&
|
|
132
|
+
typeof value.key === "string" &&
|
|
133
|
+
/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(value.key) &&
|
|
134
|
+
typeof value.section === "string" &&
|
|
135
|
+
this.isSingleLine(value.section) &&
|
|
136
|
+
typeof value.command === "string" &&
|
|
137
|
+
this.isSingleLine(value.command) &&
|
|
138
|
+
(value.description === undefined ||
|
|
139
|
+
(typeof value.description === "string" && this.isSingleLine(value.description))));
|
|
140
|
+
}
|
|
141
|
+
static isSingleLine(text) {
|
|
142
|
+
return text.trim().length > 0 && !/[\r\n]/.test(text);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Bootstrapper } from "./Bootstrapper.js";
|
|
2
|
+
export class RegisterProviders extends Bootstrapper {
|
|
3
|
+
static providers = [];
|
|
4
|
+
static merge(providers) {
|
|
5
|
+
this.providers = [...new Set(providers.filter((provider) => provider != null))];
|
|
6
|
+
}
|
|
7
|
+
bootstrap(application) {
|
|
8
|
+
for (const Provider of application.bootstrapProviders(RegisterProviders.providers)) {
|
|
9
|
+
application.register(Provider);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Bootstrapper } from "./Bootstrapper.js";
|
|
2
|
+
import { LoadProjectConfiguration } from "./LoadProjectConfiguration.js";
|
|
3
|
+
import { Repository } from "../../Support/Repository.js";
|
|
4
|
+
export class RequireProjectConfiguration extends Bootstrapper {
|
|
5
|
+
bootstrap(application) {
|
|
6
|
+
const args = application.value("console.args");
|
|
7
|
+
if (!(args instanceof Repository)) {
|
|
8
|
+
throw new Error("Console arguments must be loaded before requiring project Config.");
|
|
9
|
+
}
|
|
10
|
+
if (args.array("commandPath")[0] === "init")
|
|
11
|
+
return;
|
|
12
|
+
LoadProjectConfiguration.requireCurrent(application);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { bootService, Service } from "./Service.js";
|
|
3
|
+
function isClassConstructor(value) {
|
|
4
|
+
return (typeof value === "function" &&
|
|
5
|
+
/^class(?:\s|\{)/.test(Function.prototype.toString.call(value)) &&
|
|
6
|
+
typeof value.prototype === "object");
|
|
7
|
+
}
|
|
8
|
+
function normalizeKey(key) {
|
|
9
|
+
if (typeof key === "string") {
|
|
10
|
+
if (key.length === 0)
|
|
11
|
+
throw new TypeError("Dependency keys must be non-empty strings.");
|
|
12
|
+
return { name: key };
|
|
13
|
+
}
|
|
14
|
+
if (!isClassConstructor(key)) {
|
|
15
|
+
throw new TypeError("Dependency keys must be strings or class constructors.");
|
|
16
|
+
}
|
|
17
|
+
if (key.name.length === 0) {
|
|
18
|
+
throw new TypeError("Class dependency keys must have a name.");
|
|
19
|
+
}
|
|
20
|
+
return { name: key.name, classKey: key };
|
|
21
|
+
}
|
|
22
|
+
function isServiceConstructor(key) {
|
|
23
|
+
return key === Service || key.prototype instanceof Service;
|
|
24
|
+
}
|
|
25
|
+
export class Container {
|
|
26
|
+
bindings = new Map();
|
|
27
|
+
singletons = new Map();
|
|
28
|
+
inFlight = new Map();
|
|
29
|
+
resolution = new AsyncLocalStorage();
|
|
30
|
+
bind(key, factory) {
|
|
31
|
+
this.registerFactory(key, "transient", factory);
|
|
32
|
+
}
|
|
33
|
+
singleton(key, factory) {
|
|
34
|
+
this.registerFactory(key, "singleton", factory);
|
|
35
|
+
}
|
|
36
|
+
instance(key, value) {
|
|
37
|
+
const normalized = normalizeKey(key);
|
|
38
|
+
this.assertUnbound(normalized);
|
|
39
|
+
this.assertInstanceType(normalized, value);
|
|
40
|
+
if (value instanceof Service && !value.isBooted) {
|
|
41
|
+
throw new Error(`Supplied service instance '${normalized.name}' must already be booted.`);
|
|
42
|
+
}
|
|
43
|
+
this.bindings.set(normalized.name, {
|
|
44
|
+
kind: "instance",
|
|
45
|
+
value,
|
|
46
|
+
classKey: normalized.classKey,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
bound(key) {
|
|
50
|
+
return this.bindings.has(normalizeKey(key).name);
|
|
51
|
+
}
|
|
52
|
+
value(key) {
|
|
53
|
+
const normalized = normalizeKey(key);
|
|
54
|
+
const binding = this.binding(normalized);
|
|
55
|
+
if (binding.kind !== "instance") {
|
|
56
|
+
throw new Error(`Value '${normalized.name}' must be registered as an instance for synchronous access.`);
|
|
57
|
+
}
|
|
58
|
+
return binding.value;
|
|
59
|
+
}
|
|
60
|
+
async make(key) {
|
|
61
|
+
const normalized = normalizeKey(key);
|
|
62
|
+
const binding = this.binding(normalized);
|
|
63
|
+
if (binding.kind === "instance") {
|
|
64
|
+
this.assertInstanceType(normalized, binding.value);
|
|
65
|
+
return binding.value;
|
|
66
|
+
}
|
|
67
|
+
if (this.singletons.has(normalized.name)) {
|
|
68
|
+
const value = this.singletons.get(normalized.name);
|
|
69
|
+
this.assertInstanceType(normalized, value);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
const chain = this.resolution.getStore() ?? [];
|
|
73
|
+
this.assertNoAncestorCycle(normalized.name, chain);
|
|
74
|
+
let initialization = binding.kind === "singleton" ? this.inFlight.get(normalized.name) : undefined;
|
|
75
|
+
if (!initialization) {
|
|
76
|
+
const token = { key: normalized.name, dependencies: new Map() };
|
|
77
|
+
const promise = Promise.resolve().then(() => this.constructAndBoot(token, chain, binding.factory, normalized.classKey));
|
|
78
|
+
initialization = { token, promise };
|
|
79
|
+
if (binding.kind === "singleton") {
|
|
80
|
+
this.inFlight.set(normalized.name, initialization);
|
|
81
|
+
void promise.then((initialized) => {
|
|
82
|
+
if (this.inFlight.get(normalized.name)?.promise === promise) {
|
|
83
|
+
this.singletons.set(normalized.name, initialized);
|
|
84
|
+
this.inFlight.delete(normalized.name);
|
|
85
|
+
}
|
|
86
|
+
}, () => {
|
|
87
|
+
if (this.inFlight.get(normalized.name)?.promise === promise) {
|
|
88
|
+
this.inFlight.delete(normalized.name);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const unlink = this.linkDependency(chain.at(-1), initialization.token);
|
|
94
|
+
try {
|
|
95
|
+
const value = await initialization.promise;
|
|
96
|
+
this.assertInstanceType(normalized, value);
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
unlink();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
binding(key) {
|
|
104
|
+
const binding = this.bindings.get(key.name);
|
|
105
|
+
if (!binding)
|
|
106
|
+
throw new Error(`Unbound dependency: ${key.name}.`);
|
|
107
|
+
if (key.classKey && binding.classKey && key.classKey !== binding.classKey) {
|
|
108
|
+
throw new Error(`Dependency '${key.name}' is bound to a different constructor.`);
|
|
109
|
+
}
|
|
110
|
+
return binding;
|
|
111
|
+
}
|
|
112
|
+
assertUnbound(key) {
|
|
113
|
+
if (this.bindings.has(key.name)) {
|
|
114
|
+
throw new Error(`Dependency '${key.name}' is already bound.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
assertInstanceType(key, value) {
|
|
118
|
+
if (key.classKey && !(value instanceof key.classKey)) {
|
|
119
|
+
throw new Error(`Dependency '${key.name}' did not produce an instance of its class key.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
registerFactory(key, kind, factory) {
|
|
123
|
+
const normalized = normalizeKey(key);
|
|
124
|
+
this.assertUnbound(normalized);
|
|
125
|
+
if (!factory) {
|
|
126
|
+
if (!normalized.classKey || !isServiceConstructor(normalized.classKey)) {
|
|
127
|
+
throw new Error(`Dependency '${normalized.name}' requires an explicit factory.`);
|
|
128
|
+
}
|
|
129
|
+
const ServiceType = normalized.classKey;
|
|
130
|
+
factory = (application) => new ServiceType(application);
|
|
131
|
+
}
|
|
132
|
+
this.bindings.set(normalized.name, {
|
|
133
|
+
kind,
|
|
134
|
+
factory,
|
|
135
|
+
classKey: normalized.classKey,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
assertNoAncestorCycle(key, chain) {
|
|
139
|
+
const start = chain.findIndex((token) => token.key === key);
|
|
140
|
+
if (start >= 0) {
|
|
141
|
+
throw new Error(`Circular service dependency: ${[...chain.slice(start).map((token) => token.key), key].join(" -> ")}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
linkDependency(parent, target) {
|
|
145
|
+
if (!parent)
|
|
146
|
+
return () => { };
|
|
147
|
+
const path = this.findPath(target, parent);
|
|
148
|
+
if (path) {
|
|
149
|
+
throw new Error(`Circular service dependency: ${[parent, ...path].map((token) => token.key).join(" -> ")}`);
|
|
150
|
+
}
|
|
151
|
+
parent.dependencies.set(target, (parent.dependencies.get(target) ?? 0) + 1);
|
|
152
|
+
return () => {
|
|
153
|
+
const references = parent.dependencies.get(target);
|
|
154
|
+
if (references === 1)
|
|
155
|
+
parent.dependencies.delete(target);
|
|
156
|
+
else if (references !== undefined)
|
|
157
|
+
parent.dependencies.set(target, references - 1);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
findPath(start, target) {
|
|
161
|
+
const visit = (current, visited) => {
|
|
162
|
+
if (current === target)
|
|
163
|
+
return [current];
|
|
164
|
+
if (visited.has(current))
|
|
165
|
+
return undefined;
|
|
166
|
+
visited.add(current);
|
|
167
|
+
for (const dependency of current.dependencies.keys()) {
|
|
168
|
+
const path = visit(dependency, visited);
|
|
169
|
+
if (path)
|
|
170
|
+
return [current, ...path];
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
};
|
|
174
|
+
return visit(start, new Set());
|
|
175
|
+
}
|
|
176
|
+
async constructAndBoot(token, parentChain, factory, constructor) {
|
|
177
|
+
return this.resolution.run([...parentChain, token], async () => {
|
|
178
|
+
const value = await factory(this.owner);
|
|
179
|
+
this.assertInstanceType({ name: token.key, classKey: constructor }, value);
|
|
180
|
+
if (value instanceof Service && !value.isBooted)
|
|
181
|
+
await bootService(value);
|
|
182
|
+
return value;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DiscoverGitRoot } from "./Bootstrap/DiscoverGitRoot.js";
|
|
2
|
+
import { LoadConsoleArgs } from "./Bootstrap/LoadConsoleArgs.js";
|
|
3
|
+
import { LoadProjectConfiguration } from "./Bootstrap/LoadProjectConfiguration.js";
|
|
4
|
+
import { RegisterProviders } from "./Bootstrap/RegisterProviders.js";
|
|
5
|
+
import { RequireProjectConfiguration } from "./Bootstrap/RequireProjectConfiguration.js";
|
|
6
|
+
export class Kernel {
|
|
7
|
+
app;
|
|
8
|
+
constructor(app) {
|
|
9
|
+
this.app = app;
|
|
10
|
+
}
|
|
11
|
+
bootstrappers() {
|
|
12
|
+
return [
|
|
13
|
+
LoadConsoleArgs,
|
|
14
|
+
DiscoverGitRoot,
|
|
15
|
+
LoadProjectConfiguration,
|
|
16
|
+
RequireProjectConfiguration,
|
|
17
|
+
RegisterProviders,
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
async bootstrap() {
|
|
21
|
+
await this.app.bootstrapWith(this.bootstrappers());
|
|
22
|
+
}
|
|
23
|
+
async boot(invocation) {
|
|
24
|
+
if (!this.app.bound("console.invocation")) {
|
|
25
|
+
this.app.instance("console.invocation", invocation);
|
|
26
|
+
}
|
|
27
|
+
await this.bootstrap();
|
|
28
|
+
await this.app.boot();
|
|
29
|
+
}
|
|
30
|
+
}
|