@cosmicdrift/kumiko-framework 0.348.1 → 0.349.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/boot-validator/screens.js +34 -2
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/upgrade-cli.d.ts +12 -0
- package/dist/upgrade-cli.js +231 -52
- package/package.json +4 -4
- package/src/changes.json +7 -0
|
@@ -1746,6 +1746,33 @@ function validateDashboardStatGroupPanel(featureName, screenId, panel, addPanelI
|
|
|
1746
1746
|
if (stat.valueField.length === 0) {
|
|
1747
1747
|
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (dashboard) stat-group "${panel.id}" child "${stat.id}" has empty valueField.`);
|
|
1748
1748
|
}
|
|
1749
|
+
validateDashboardValueFormat(featureName, screenId, stat.id, stat.valueFormat);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
function validateDashboardChartDisplay(featureName, screenId, panel) {
|
|
1753
|
+
validateDashboardValueFormat(featureName, screenId, panel.id, panel.valueFormat);
|
|
1754
|
+
const where = `[Feature ${featureName}] Screen "${screenId}" (dashboard) chart-panel "${panel.id}"`;
|
|
1755
|
+
if (panel.scrollable !== undefined && typeof panel.scrollable !== "boolean") {
|
|
1756
|
+
throw new Error(`${where} has a non-boolean scrollable.`);
|
|
1757
|
+
}
|
|
1758
|
+
if (panel.scrollable === true && panel.chart !== "stacked-area") {
|
|
1759
|
+
throw new Error(`${where} sets scrollable on chart "${panel.chart}" — only "stacked-area" supports it.`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const CURRENCY_CODE_RE = /^[A-Z]{3}$/;
|
|
1763
|
+
const MAX_VALUE_FORMAT_FRACTION_DIGITS = 4;
|
|
1764
|
+
function validateDashboardValueFormat(featureName, screenId, panelId, valueFormat) {
|
|
1765
|
+
// skip: valueFormat is optional, nothing to validate without it
|
|
1766
|
+
if (valueFormat === undefined)
|
|
1767
|
+
return;
|
|
1768
|
+
const where = `[Feature ${featureName}] Screen "${screenId}" (dashboard) panel "${panelId}" valueFormat`;
|
|
1769
|
+
if (!CURRENCY_CODE_RE.test(valueFormat.currency)) {
|
|
1770
|
+
throw new Error(`${where}.currency "${String(valueFormat.currency)}" must be a 3-letter uppercase ISO currency code.`);
|
|
1771
|
+
}
|
|
1772
|
+
const digits = valueFormat.fractionDigits;
|
|
1773
|
+
if (digits !== undefined &&
|
|
1774
|
+
(!Number.isInteger(digits) || digits < 0 || digits > MAX_VALUE_FORMAT_FRACTION_DIGITS)) {
|
|
1775
|
+
throw new Error(`${where}.fractionDigits "${String(digits)}" must be an integer from 0 to ${MAX_VALUE_FORMAT_FRACTION_DIGITS}.`);
|
|
1749
1776
|
}
|
|
1750
1777
|
}
|
|
1751
1778
|
export function validateDashboardCustomPanel(featureName, screenId, panel) {
|
|
@@ -1764,9 +1791,14 @@ function validateDashboardQueryPanel(featureName, screenId, panel, featureMap) {
|
|
|
1764
1791
|
panel.span !== "full") {
|
|
1765
1792
|
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (dashboard) panel "${panel.id}" has span "${String(panel.span)}" — expected "half" or "full".`);
|
|
1766
1793
|
}
|
|
1767
|
-
if (panel.kind === "stat"
|
|
1768
|
-
|
|
1794
|
+
if (panel.kind === "stat") {
|
|
1795
|
+
if (panel.valueField.length === 0) {
|
|
1796
|
+
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (dashboard) stat-panel "${panel.id}" has empty valueField.`);
|
|
1797
|
+
}
|
|
1798
|
+
validateDashboardValueFormat(featureName, screenId, panel.id, panel.valueFormat);
|
|
1769
1799
|
}
|
|
1800
|
+
if (panel.kind === "chart")
|
|
1801
|
+
validateDashboardChartDisplay(featureName, screenId, panel);
|
|
1770
1802
|
if (panel.kind === "list") {
|
|
1771
1803
|
if (panel.columns.length === 0) {
|
|
1772
1804
|
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (dashboard) list-panel "${panel.id}" has an empty columns list.`);
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
|
|
|
74
74
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
75
75
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
76
76
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
77
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
77
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
78
78
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
79
79
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
80
80
|
export { runValidation } from "./validation.js";
|
|
@@ -17,7 +17,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
|
|
|
17
17
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
18
|
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
19
19
|
export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
|
|
20
|
-
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
20
|
+
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
21
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
22
22
|
export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
|
|
23
23
|
export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefin
|
|
|
9
9
|
export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.js";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
11
11
|
export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.js";
|
|
12
|
-
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartPanel, DashboardChartTone, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardTimeRangeDefinition, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
12
|
+
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartPanel, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
13
13
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
14
14
|
export type { TargetRef } from "../engine/types/target-ref.js";
|
|
15
15
|
export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.js";
|
package/dist/upgrade-cli.d.ts
CHANGED
|
@@ -7,6 +7,18 @@ export declare function findPackageChangelogFiles(cwd: string): string[];
|
|
|
7
7
|
export declare function findFeaturesDirs(cwd: string): string[];
|
|
8
8
|
export declare function findCodemodScriptsRoot(repoRoot: string): string | null;
|
|
9
9
|
export declare function resolveCodemodScript(repoRoot: string, codemodField: string | undefined): string | null;
|
|
10
|
+
export type UpgradeMarkerManual = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly version: string;
|
|
13
|
+
readonly title: string;
|
|
14
|
+
};
|
|
15
|
+
export type ManualResolution = "migrated" | "not-applicable";
|
|
16
|
+
export type UpgradeMarkerResolvedManual = UpgradeMarkerManual & {
|
|
17
|
+
readonly resolution: ManualResolution;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
readonly resolvedAt: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function manualEntryId(version: string, title: string): string;
|
|
10
22
|
export declare function runUpgradeCli(argv: readonly string[], cwd: string, out: UpgradeCliOut, options?: {
|
|
11
23
|
readonly repoRoot?: string;
|
|
12
24
|
}): Promise<number>;
|
package/dist/upgrade-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
2
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
4
|
import { getFlag, getStringFlag, parseArgs } from "./arg-parser.js";
|
|
@@ -259,71 +260,153 @@ async function runCodemodScript(scriptPath, targetDir, repoRoot, dryRun) {
|
|
|
259
260
|
function hasCodemod(e) {
|
|
260
261
|
return typeof e.codemod === "string" && e.codemod.length > 0;
|
|
261
262
|
}
|
|
263
|
+
const MANUAL_ID_HASH_LENGTH = 8;
|
|
264
|
+
const MAX_RESOLVE_REASON_LENGTH = 500;
|
|
265
|
+
// The hash tells apart entries that share a version (studio has several at 0.341.0).
|
|
266
|
+
export function manualEntryId(version, title) {
|
|
267
|
+
const hash = createHash("sha256").update(title).digest("hex").slice(0, MANUAL_ID_HASH_LENGTH);
|
|
268
|
+
return `${version}:${hash}`;
|
|
269
|
+
}
|
|
270
|
+
function toMarkerManual(entry) {
|
|
271
|
+
return {
|
|
272
|
+
id: manualEntryId(entry.version, entry.title),
|
|
273
|
+
version: entry.version,
|
|
274
|
+
title: entry.title,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function dedupeManualById(entries) {
|
|
278
|
+
const seen = new Set();
|
|
279
|
+
return entries.filter((entry) => {
|
|
280
|
+
if (seen.has(entry.id))
|
|
281
|
+
return false;
|
|
282
|
+
seen.add(entry.id);
|
|
283
|
+
return true;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
// Open manual items survive every marker write until --resolve moves them.
|
|
287
|
+
function carryOverManual(previous, newManual) {
|
|
288
|
+
const resolvedIds = new Set(previous.resolvedManual.map((entry) => entry.id));
|
|
289
|
+
const pendingManual = dedupeManualById([...previous.pendingManual, ...newManual]).filter((entry) => !resolvedIds.has(entry.id));
|
|
290
|
+
return {
|
|
291
|
+
...(pendingManual.length > 0 && { pendingManual }),
|
|
292
|
+
...(previous.resolvedManual.length > 0 && { resolvedManual: previous.resolvedManual }),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
262
295
|
function writeUpgradeMarker(targetDir, marker) {
|
|
263
296
|
const dir = join(targetDir, ".kumiko");
|
|
264
297
|
mkdirSync(dir, { recursive: true });
|
|
265
298
|
writeFileSync(join(dir, "upgrade-state.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf-8");
|
|
266
299
|
}
|
|
300
|
+
const NO_CARRY = { pendingManual: [], resolvedManual: [] };
|
|
301
|
+
function isRecord(value) {
|
|
302
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
303
|
+
}
|
|
304
|
+
function parseManualEntry(value) {
|
|
305
|
+
if (!isRecord(value))
|
|
306
|
+
return null;
|
|
307
|
+
const { id, version, title } = value;
|
|
308
|
+
if (typeof version !== "string" || typeof title !== "string")
|
|
309
|
+
return null;
|
|
310
|
+
return {
|
|
311
|
+
id: typeof id === "string" && id.length > 0 ? id : manualEntryId(version, title),
|
|
312
|
+
version,
|
|
313
|
+
title,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function parseResolvedEntry(value) {
|
|
317
|
+
const base = parseManualEntry(value);
|
|
318
|
+
if (base === null || !isRecord(value))
|
|
319
|
+
return null;
|
|
320
|
+
const { resolution, reason, resolvedAt } = value;
|
|
321
|
+
if (resolution !== "migrated" && resolution !== "not-applicable")
|
|
322
|
+
return null;
|
|
323
|
+
if (typeof reason !== "string" || typeof resolvedAt !== "string")
|
|
324
|
+
return null;
|
|
325
|
+
return { ...base, resolution, reason, resolvedAt };
|
|
326
|
+
}
|
|
327
|
+
function parseList(value, parseEntry) {
|
|
328
|
+
if (value === undefined)
|
|
329
|
+
return [];
|
|
330
|
+
if (!Array.isArray(value))
|
|
331
|
+
return null;
|
|
332
|
+
const parsed = value.map(parseEntry);
|
|
333
|
+
return parsed.every((entry) => entry !== null) ? parsed : null;
|
|
334
|
+
}
|
|
335
|
+
function isMarkerCodemod(value) {
|
|
336
|
+
return (isRecord(value) &&
|
|
337
|
+
typeof value["version"] === "string" &&
|
|
338
|
+
typeof value["codemod"] === "string" &&
|
|
339
|
+
typeof value["title"] === "string");
|
|
340
|
+
}
|
|
341
|
+
function parseStoredMarker(raw) {
|
|
342
|
+
if (!isRecord(raw))
|
|
343
|
+
return null;
|
|
344
|
+
const { version, appliedAt, codemods } = raw;
|
|
345
|
+
if (typeof version !== "string" || !SEMVER_RE.test(version))
|
|
346
|
+
return null;
|
|
347
|
+
const pendingManual = parseList(raw["pendingManual"], parseManualEntry);
|
|
348
|
+
const resolvedManual = parseList(raw["resolvedManual"], parseResolvedEntry);
|
|
349
|
+
if (pendingManual === null || resolvedManual === null)
|
|
350
|
+
return null;
|
|
351
|
+
return {
|
|
352
|
+
version,
|
|
353
|
+
appliedAt: typeof appliedAt === "string" ? appliedAt : Temporal.Now.instant().toString(),
|
|
354
|
+
codemods: Array.isArray(codemods) ? codemods.filter(isMarkerCodemod) : [],
|
|
355
|
+
pendingManual: dedupeManualById(pendingManual),
|
|
356
|
+
resolvedManual: dedupeManualById(resolvedManual),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
267
359
|
// A missing marker is a bootstrap app that never ran `--apply` (fw#2299); an
|
|
268
360
|
// existing but unreadable one must not silently fall back to the installed
|
|
269
361
|
// version, or a bare `--apply` would skip every codemod in between.
|
|
270
|
-
function
|
|
362
|
+
function readMarker(targetDir) {
|
|
271
363
|
const markerPath = join(targetDir, ".kumiko", "upgrade-state.json");
|
|
272
364
|
if (!existsSync(markerPath))
|
|
273
365
|
return { kind: "missing" };
|
|
274
366
|
try {
|
|
275
|
-
const
|
|
276
|
-
return
|
|
277
|
-
? { kind: "ok", version: parsed.version }
|
|
278
|
-
: { kind: "invalid" };
|
|
367
|
+
const marker = parseStoredMarker(JSON.parse(readFileSync(markerPath, "utf-8")));
|
|
368
|
+
return marker === null ? { kind: "invalid" } : { kind: "ok", version: marker.version, marker };
|
|
279
369
|
}
|
|
280
370
|
catch {
|
|
281
371
|
return { kind: "invalid" };
|
|
282
372
|
}
|
|
283
373
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const headEligible = eligible[0];
|
|
292
|
-
if (headEligible !== undefined) {
|
|
293
|
-
return eligible.reduce((max, e) => (compareVersions(e.version, max) > 0 ? e.version : max), headEligible.version);
|
|
294
|
-
}
|
|
295
|
-
const nonBreaking = pending.filter((e) => e.type !== "breaking");
|
|
296
|
-
const headNonBreaking = nonBreaking[0];
|
|
297
|
-
if (headNonBreaking !== undefined) {
|
|
298
|
-
return nonBreaking.reduce((max, e) => (compareVersions(e.version, max) > 0 ? e.version : max), headNonBreaking.version);
|
|
299
|
-
}
|
|
300
|
-
return fallbackInstalled;
|
|
374
|
+
function carryOf(read) {
|
|
375
|
+
return read.kind === "ok" ? read.marker : NO_CARRY;
|
|
376
|
+
}
|
|
377
|
+
// The marker file is editable repo content; a tampered title must not reach
|
|
378
|
+
// the terminal as an escape sequence.
|
|
379
|
+
function printable(text) {
|
|
380
|
+
return text.replace(/\p{Cc}/gu, "");
|
|
301
381
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
382
|
+
function logOpenManual(out, open) {
|
|
383
|
+
// skip: no open manual migrations, nothing to report
|
|
384
|
+
if (open.length === 0)
|
|
385
|
+
return;
|
|
386
|
+
out.log(` ⚠ ${open.length} manual migration(s) still open:`);
|
|
387
|
+
for (const entry of open) {
|
|
388
|
+
out.log(` ${printable(entry.id)} · ${printable(entry.version)} · ${printable(entry.title)}`);
|
|
308
389
|
}
|
|
390
|
+
out.log(' Migrate them by hand, then mark them done: kumiko-upgrade --resolve <id|version> --reason "<what you did>" [--not-applicable]');
|
|
309
391
|
}
|
|
310
392
|
// Records the codemods that already wrote files before a later one failed, so
|
|
311
393
|
// a re-run resumes after them instead of replaying non-idempotent codemods.
|
|
312
|
-
function writePartialMarker(out, targetDir, pending, manualEntries, failedVersion, ran) {
|
|
394
|
+
function writePartialMarker(out, targetDir, pending, manualEntries, failedVersion, ran, previous) {
|
|
313
395
|
// skip: no codemod wrote files before the failure, nothing to resume after
|
|
314
396
|
if (ran.length === 0)
|
|
315
397
|
return;
|
|
316
|
-
const
|
|
317
|
-
|
|
398
|
+
const versionsBeforeFailure = pending
|
|
399
|
+
.map((e) => e.version)
|
|
400
|
+
.filter((version) => compareVersions(version, failedVersion) < 0);
|
|
401
|
+
const version = versionsBeforeFailure.reduce((max, candidate) => (max === null || compareVersions(candidate, max) > 0 ? candidate : max), null);
|
|
318
402
|
// skip: no pending entry completed before the failure, so no marker version to record
|
|
319
|
-
if (version ===
|
|
403
|
+
if (version === null)
|
|
320
404
|
return;
|
|
321
|
-
const pendingManual = manualEntries.map((e) => ({ version: e.version, title: e.title }));
|
|
322
405
|
writeUpgradeMarker(targetDir, {
|
|
323
406
|
version,
|
|
324
407
|
appliedAt: Temporal.Now.instant().toString(),
|
|
325
408
|
codemods: ran,
|
|
326
|
-
...(
|
|
409
|
+
...carryOverManual(previous, manualEntries.map(toMarkerManual)),
|
|
327
410
|
});
|
|
328
411
|
out.err(` ⚠ ${ran.length} codemod(s) already applied; wrote marker at ${version} so a re-run resumes at the failed one.`);
|
|
329
412
|
}
|
|
@@ -338,16 +421,19 @@ async function applyCodemods(out, pending, repoRoot, targetDir, dryRun,
|
|
|
338
421
|
// Installed version for the zero-pending bootstrap marker — never a
|
|
339
422
|
// `--from` filter override (that would permanently skip real pending
|
|
340
423
|
// codemods once the CI guard compares against the fake marker).
|
|
341
|
-
markerVersion) {
|
|
424
|
+
markerVersion, previous) {
|
|
342
425
|
if (pending.length === 0) {
|
|
343
426
|
out.log(" ✓ Nothing new since your version.");
|
|
344
427
|
if (!dryRun) {
|
|
428
|
+
const carried = carryOverManual(previous, []);
|
|
345
429
|
writeUpgradeMarker(targetDir, {
|
|
346
430
|
version: markerVersion,
|
|
347
431
|
appliedAt: Temporal.Now.instant().toString(),
|
|
348
432
|
codemods: [],
|
|
433
|
+
...carried,
|
|
349
434
|
});
|
|
350
435
|
out.log(` ✓ Applied 0 codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
436
|
+
logOpenManual(out, carried.pendingManual ?? []);
|
|
351
437
|
}
|
|
352
438
|
return 0;
|
|
353
439
|
}
|
|
@@ -364,16 +450,15 @@ markerVersion) {
|
|
|
364
450
|
? " No automatable codemods among the pending breaking changes."
|
|
365
451
|
: " ✓ No breaking changes pending.");
|
|
366
452
|
if (!dryRun) {
|
|
367
|
-
const
|
|
368
|
-
const pendingManual = manualEntries.map((e) => ({ version: e.version, title: e.title }));
|
|
453
|
+
const carried = carryOverManual(previous, manualEntries.map(toMarkerManual));
|
|
369
454
|
writeUpgradeMarker(targetDir, {
|
|
370
|
-
version:
|
|
455
|
+
version: markerVersion,
|
|
371
456
|
appliedAt: Temporal.Now.instant().toString(),
|
|
372
457
|
codemods: [],
|
|
373
|
-
...
|
|
458
|
+
...carried,
|
|
374
459
|
});
|
|
375
460
|
out.log(` ✓ Applied 0 codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
376
|
-
|
|
461
|
+
logOpenManual(out, carried.pendingManual ?? []);
|
|
377
462
|
}
|
|
378
463
|
return 0;
|
|
379
464
|
}
|
|
@@ -388,7 +473,7 @@ markerVersion) {
|
|
|
388
473
|
if (!scriptPath) {
|
|
389
474
|
out.err(` ✗ ${e.version} · ${e.title} — invalid codemod path "${e.codemod}"`);
|
|
390
475
|
if (!dryRun)
|
|
391
|
-
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran);
|
|
476
|
+
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran, previous);
|
|
392
477
|
return 1;
|
|
393
478
|
}
|
|
394
479
|
out.log(` → ${e.version} · running ${e.codemod}${dryRun ? " (dry-run)" : ""}`);
|
|
@@ -398,7 +483,7 @@ markerVersion) {
|
|
|
398
483
|
if (!result.ok) {
|
|
399
484
|
out.err(` ✗ ${e.version} · ${e.codemod} failed`);
|
|
400
485
|
if (!dryRun)
|
|
401
|
-
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran);
|
|
486
|
+
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran, previous);
|
|
402
487
|
return 1;
|
|
403
488
|
}
|
|
404
489
|
ran.push({ version: e.version, codemod: e.codemod, title: e.title });
|
|
@@ -407,28 +492,114 @@ markerVersion) {
|
|
|
407
492
|
out.log(` ✓ Dry-run: ${ran.length} codemod(s) would run. Nothing written.`);
|
|
408
493
|
return 0;
|
|
409
494
|
}
|
|
410
|
-
const
|
|
411
|
-
const pendingManual = manualEntries.map((e) => ({ version: e.version, title: e.title }));
|
|
495
|
+
const carried = carryOverManual(previous, manualEntries.map(toMarkerManual));
|
|
412
496
|
writeUpgradeMarker(targetDir, {
|
|
413
|
-
version:
|
|
497
|
+
version: markerVersion,
|
|
414
498
|
appliedAt: Temporal.Now.instant().toString(),
|
|
415
499
|
codemods: ran,
|
|
416
|
-
...
|
|
500
|
+
...carried,
|
|
417
501
|
});
|
|
418
502
|
out.log(` ✓ Applied ${ran.length} codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
419
|
-
|
|
420
|
-
|
|
503
|
+
logOpenManual(out, carried.pendingManual ?? []);
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
function parseResolveInput(args) {
|
|
507
|
+
if (getFlag(args, "apply"))
|
|
508
|
+
return { error: "--resolve cannot be combined with --apply." };
|
|
509
|
+
const refs = (getStringFlag(args, "resolve") ?? "")
|
|
510
|
+
.split(",")
|
|
511
|
+
.map((ref) => ref.trim())
|
|
512
|
+
.filter((ref) => ref.length > 0);
|
|
513
|
+
if (refs.length === 0)
|
|
514
|
+
return { error: "--resolve needs at least one id or version." };
|
|
515
|
+
const reason = getStringFlag(args, "reason")?.trim() ?? "";
|
|
516
|
+
if (reason.length === 0)
|
|
517
|
+
return { error: '--resolve requires --reason "<text>".' };
|
|
518
|
+
if (reason.length > MAX_RESOLVE_REASON_LENGTH) {
|
|
519
|
+
return { error: `--reason is too long (max ${MAX_RESOLVE_REASON_LENGTH} characters).` };
|
|
520
|
+
}
|
|
521
|
+
return { refs, reason };
|
|
522
|
+
}
|
|
523
|
+
function matchManualRefs(open, refs) {
|
|
524
|
+
const resolvedIds = new Set();
|
|
525
|
+
const errors = [];
|
|
526
|
+
for (const ref of refs) {
|
|
527
|
+
const byId = open.find((entry) => entry.id === ref);
|
|
528
|
+
const byVersion = open.filter((entry) => entry.version === ref);
|
|
529
|
+
if (byId !== undefined) {
|
|
530
|
+
resolvedIds.add(byId.id);
|
|
531
|
+
}
|
|
532
|
+
else if (byVersion.length === 1 && byVersion[0] !== undefined) {
|
|
533
|
+
resolvedIds.add(byVersion[0].id);
|
|
534
|
+
}
|
|
535
|
+
else if (byVersion.length > 1) {
|
|
536
|
+
errors.push(`"${ref}" matches ${byVersion.length} open entries, pass one of: ${byVersion.map((entry) => entry.id).join(", ")}`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
errors.push(`"${ref}" matches no open manual migration`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return { resolvedIds, errors };
|
|
543
|
+
}
|
|
544
|
+
function resolveManualMigrations(out, args, targetDir) {
|
|
545
|
+
const fail = (...lines) => {
|
|
546
|
+
out.err("");
|
|
547
|
+
for (const line of lines)
|
|
548
|
+
out.err(` ${line}`);
|
|
549
|
+
out.err("");
|
|
550
|
+
return 1;
|
|
551
|
+
};
|
|
552
|
+
const input = parseResolveInput(args);
|
|
553
|
+
if ("error" in input)
|
|
554
|
+
return fail(input.error);
|
|
555
|
+
const read = readMarker(targetDir);
|
|
556
|
+
if (read.kind === "missing") {
|
|
557
|
+
return fail(`No .kumiko/upgrade-state.json under ${targetDir}, nothing to resolve.`);
|
|
558
|
+
}
|
|
559
|
+
if (read.kind === "invalid")
|
|
560
|
+
return fail("invalid .kumiko/upgrade-state.json, fix it first.");
|
|
561
|
+
const open = read.marker.pendingManual;
|
|
562
|
+
const { resolvedIds, errors } = matchManualRefs(open, input.refs);
|
|
563
|
+
if (errors.length > 0)
|
|
564
|
+
return fail(...errors, "Nothing was written.");
|
|
565
|
+
const { reason } = input;
|
|
566
|
+
const notApplicableFlag = args.flags.get("not-applicable");
|
|
567
|
+
const resolution = notApplicableFlag !== undefined && notApplicableFlag !== false ? "not-applicable" : "migrated";
|
|
568
|
+
const resolvedAt = Temporal.Now.instant().toString();
|
|
569
|
+
const newlyResolved = open
|
|
570
|
+
.filter((entry) => resolvedIds.has(entry.id))
|
|
571
|
+
.map((entry) => ({ ...entry, resolution, reason, resolvedAt }));
|
|
572
|
+
const remaining = open.filter((entry) => !resolvedIds.has(entry.id));
|
|
573
|
+
const resolvedManual = dedupeManualById([...read.marker.resolvedManual, ...newlyResolved]);
|
|
574
|
+
writeUpgradeMarker(targetDir, {
|
|
575
|
+
version: read.marker.version,
|
|
576
|
+
appliedAt: read.marker.appliedAt,
|
|
577
|
+
codemods: read.marker.codemods,
|
|
578
|
+
...(remaining.length > 0 && { pendingManual: remaining }),
|
|
579
|
+
resolvedManual,
|
|
580
|
+
});
|
|
581
|
+
out.log("");
|
|
582
|
+
out.log(` ✓ Marked ${newlyResolved.length} manual migration(s) as ${resolution}:`);
|
|
583
|
+
for (const entry of newlyResolved) {
|
|
584
|
+
out.log(` ${printable(entry.id)} · ${printable(entry.title)}`);
|
|
421
585
|
}
|
|
422
|
-
|
|
586
|
+
if (remaining.length > 0)
|
|
587
|
+
logOpenManual(out, remaining);
|
|
588
|
+
out.log("");
|
|
423
589
|
return 0;
|
|
424
590
|
}
|
|
425
591
|
const UPGRADE_USAGE = `kumiko-upgrade [--from <version>] [--dir <path>] [--json] [--verbose] [--apply [--dry-run]]
|
|
592
|
+
kumiko-upgrade --resolve <id|version>[,<id|version>...] --reason "<text>" [--not-applicable] [--dir <path>]
|
|
426
593
|
|
|
427
594
|
--from <version> filter baseline version (default: last applied marker, or the installed version)
|
|
428
595
|
--dir <path> target directory to check/apply against (default: cwd)
|
|
429
596
|
--json machine-readable output
|
|
430
597
|
--verbose include full migration details for each pending entry
|
|
431
598
|
--apply run codemods for pending breaking changes and write the upgrade marker
|
|
599
|
+
(one run moves the marker to the installed version; manual migrations stay listed as open)
|
|
600
|
+
--resolve <refs> mark open manual migrations as done; ref = full id or a version with exactly one open entry
|
|
601
|
+
--reason <text> required with --resolve, max 500 characters
|
|
602
|
+
--not-applicable with --resolve, record the entries as not applicable instead of migrated
|
|
432
603
|
--dry-run with --apply, report what would run without writing anything
|
|
433
604
|
--help, -h print this usage and exit`;
|
|
434
605
|
// kumiko-lint-ignore complexity-budget CLI orchestration moved from bin/commands/upgrade.ts — same branching surface, shared by kumiko upgrade + published kumiko-upgrade bin
|
|
@@ -454,6 +625,9 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
454
625
|
out.err("");
|
|
455
626
|
return 1;
|
|
456
627
|
}
|
|
628
|
+
if (args.flags.has("resolve")) {
|
|
629
|
+
return resolveManualMigrations(out, args, targetDir);
|
|
630
|
+
}
|
|
457
631
|
// Always resolve the actually installed version, even when --from is set,
|
|
458
632
|
// so --json can report both the filter baseline and what's really there.
|
|
459
633
|
const installedVersion = readCurrentVersion(cwd);
|
|
@@ -465,7 +639,8 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
465
639
|
// when the marker says otherwise — a bare `--apply` would then always
|
|
466
640
|
// report "Nothing new" and bootstrap the marker onto the installed
|
|
467
641
|
// version, hiding breaking changes the marker never actually saw.
|
|
468
|
-
const
|
|
642
|
+
const markerRead = readMarker(targetDir);
|
|
643
|
+
const marker = fromFlag === undefined ? markerRead : { kind: "missing" };
|
|
469
644
|
if (marker.kind === "invalid") {
|
|
470
645
|
out.err("");
|
|
471
646
|
out.err(" invalid .kumiko/upgrade-state.json, fix it or pass --from <version>");
|
|
@@ -516,23 +691,26 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
516
691
|
return 1;
|
|
517
692
|
}
|
|
518
693
|
out.log("");
|
|
519
|
-
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun, markerVersion ?? currentVersion);
|
|
694
|
+
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun, markerVersion ?? currentVersion, carryOf(markerRead));
|
|
520
695
|
out.log("");
|
|
521
696
|
return code;
|
|
522
697
|
}
|
|
523
698
|
if (jsonMode) {
|
|
524
|
-
|
|
699
|
+
const { pendingManual } = carryOf(markerRead);
|
|
700
|
+
out.log(JSON.stringify({ currentVersion, installedVersion, pending, pendingManual }, null, 2));
|
|
525
701
|
return 0;
|
|
526
702
|
}
|
|
527
703
|
const breaking = pending.filter((e) => e.type === "breaking");
|
|
528
704
|
const improvements = pending.filter((e) => e.type === "improvement");
|
|
529
705
|
const fixes = pending.filter((e) => e.type === "fix");
|
|
706
|
+
const openManual = carryOf(markerRead).pendingManual;
|
|
530
707
|
out.log("");
|
|
531
708
|
out.log(` Upgrade: ${currentVersion} → latest`);
|
|
532
709
|
out.log("");
|
|
533
710
|
if (pending.length === 0) {
|
|
534
711
|
out.log(" ✓ Nothing new since your version.");
|
|
535
712
|
out.log("");
|
|
713
|
+
logOpenManual(out, openManual);
|
|
536
714
|
return 0;
|
|
537
715
|
}
|
|
538
716
|
if (breaking.length > 0) {
|
|
@@ -576,5 +754,6 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
576
754
|
out.log(" Run with --verbose for full migration details.");
|
|
577
755
|
out.log("");
|
|
578
756
|
}
|
|
757
|
+
logOpenManual(out, openManual);
|
|
579
758
|
return 0;
|
|
580
759
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.349.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"./package.json": "./package.json"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.349.0",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.349.0",
|
|
211
211
|
"bullmq": "^5.76.7",
|
|
212
212
|
"bun-types": "^1.3.13",
|
|
213
213
|
"hono": "^4.13.1",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"zod": "^4.4.3"
|
|
224
224
|
},
|
|
225
225
|
"devDependencies": {
|
|
226
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.349.0",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.349.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "kumiko-upgrade advances in one --apply run, tracks open manual migrations with ids and resolves them via --resolve",
|
|
6
|
+
"detail": "kumiko-upgrade: one --apply run advances, manual items are resolved with --resolve\n`--apply` now moves the marker to the installed version in a single run. Breaking changes without a codemod stay in the marker as `pendingManual` with a stable id (`<version>:<8-char title hash>`) and are listed after every run; the upgrade guard no longer fails on them. `--resolve <id|version>[,...] --reason \"<text>\" [--not-applicable]` moves entries to `resolvedManual` (version must match exactly one open entry; unknown refs write nothing). Resolved ids do not come back. The plain report lists open entries, and `--json` carries them as `pendingManual`.",
|
|
7
|
+
"migration": "If you relied on a second --apply to acknowledge manual migrations, run kumiko-upgrade --resolve <id> --reason \"<text>\" instead."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.348.0",
|
|
4
11
|
"type": "improvement",
|