@cosmicdrift/kumiko-framework 0.373.0 → 0.377.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/AGENTS.md +2 -2
- package/agents/examples/apex-landing.md +1 -1
- package/agents/examples/apex-surface-auth.md +3 -3
- package/agents/examples/compose-stacks.md +15 -8
- package/agents/examples/embedded-entity-form.md +1 -1
- package/agents/examples/notes-history-basic.md +1 -1
- package/agents/examples/tags-basic.md +1 -1
- package/agents/examples/user-profile.md +1 -1
- package/agents/examples/wizard-form.md +4 -4
- package/agents/widgets.md +4 -3
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +2 -1
- package/dist/config/index.d.ts +4 -0
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/db/migrate-runner.js +12 -4
- package/dist/db/money.js +1 -1
- package/dist/db/query.js +14 -14
- package/dist/db/replay-migration-sql.d.ts +1 -0
- package/dist/db/replay-migration-sql.js +115 -11
- package/dist/db/tenant-db.d.ts +7 -0
- package/dist/db/tenant-db.js +17 -5
- package/dist/engine/boot-validator/projection-list-screens.js +28 -37
- package/dist/engine/boot-validator/query-refs.js +26 -8
- package/dist/engine/boot-validator/screens.d.ts +1 -1
- package/dist/engine/boot-validator/screens.js +334 -42
- package/dist/engine/boot-validator/url-prefill-fields.js +3 -0
- package/dist/engine/build-app-schema.js +7 -3
- package/dist/engine/entity-list-searchability.d.ts +5 -0
- package/dist/engine/entity-list-searchability.js +35 -0
- package/dist/engine/feature-ui-extensions.js +5 -2
- package/dist/engine/index.d.ts +3 -1
- package/dist/engine/index.js +2 -0
- package/dist/engine/paged-list.d.ts +29 -0
- package/dist/engine/paged-list.js +56 -0
- package/dist/engine/parse-ref-target.js +1 -1
- package/dist/engine/project-app-schema-for-roles.js +37 -6
- package/dist/engine/provider-plugin-context.d.ts +3 -0
- package/dist/engine/provider-plugin-context.js +28 -0
- package/dist/engine/schema-builder.js +7 -0
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/env/index.js +1 -1
- package/dist/errors/zod-bridge.js +1 -1
- package/dist/files/file-ref-entity.d.ts +6 -6
- package/dist/files/file-ref-table.d.ts +6 -6
- package/dist/files/index.d.ts +1 -1
- package/dist/files/index.js +1 -1
- package/dist/files/provider-resolver.d.ts +17 -17
- package/dist/files/provider-resolver.js +1 -1
- package/dist/http/egress.d.ts +14 -0
- package/dist/http/egress.js +144 -0
- package/dist/http/index.d.ts +3 -2
- package/dist/http/index.js +2 -1
- package/dist/http/policy.d.ts +30 -0
- package/dist/http/policy.js +299 -0
- package/dist/i18n/required-surface-keys.js +52 -2
- package/dist/jobs/job-runner.js +4 -2
- package/dist/pipeline/config-accessor-factory.d.ts +3 -0
- package/dist/pipeline/config-accessor-factory.js +3 -0
- package/dist/pipeline/dispatch-shared.js +7 -5
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/upgrade-changelog.d.ts +25 -0
- package/dist/upgrade-changelog.js +48 -0
- package/dist/upgrade-cli.js +78 -13
- package/package.json +3 -5
- package/presets/biome-app.json +8 -0
- package/src/changes.json +132 -0
- package/src/scripts/codemod/README.md +25 -0
- package/src/scripts/codemod/codemod-support.ts +64 -6
- package/src/scripts/codemod/package-merge.ts +260 -0
- package/src/scripts/codemod/s3-imports.ts +4 -42
- package/src/scripts/codemod/testing-injection.ts +135 -0
package/dist/jobs/job-runner.js
CHANGED
|
@@ -15,6 +15,7 @@ import { isKumikoError } from "../errors/kumiko-error.js";
|
|
|
15
15
|
import { createTenantFileContext } from "../files/file-handle.js";
|
|
16
16
|
import { createFallbackLogger } from "../logging/index.js";
|
|
17
17
|
import { createEscapeHatchProcessDedup, createEscapeHatchReporter, createEscapeHatchReportWindow, createMetricsForFactory, emitJobLastSuccess, emitJobQueueDepth, flushEscapeHatchAudits, getFallbackTracer, registerStandardMetrics, withEscapeHatchAuditScope, } from "../observability/index.js";
|
|
18
|
+
import { configAccessorFactoryOf } from "../pipeline/config-accessor-factory.js";
|
|
18
19
|
import { createDistributedLock } from "../pipeline/distributed-lock.js";
|
|
19
20
|
import { RedisKeys } from "../pipeline/redis-keys.js";
|
|
20
21
|
import { createGatedIdentitySwitch } from "../pipeline/system-identity-switch.js";
|
|
@@ -967,8 +968,9 @@ export function createJobRunner(options) {
|
|
|
967
968
|
const crossTenantReads = tenantScopedDb && crossTenantReadsReason !== undefined
|
|
968
969
|
? createCrossTenantReads(tenantScopedDb, crossTenantReadsReason, reportEscapeHatch)
|
|
969
970
|
: undefined;
|
|
970
|
-
const
|
|
971
|
-
|
|
971
|
+
const configFactory = configAccessorFactoryOf(context);
|
|
972
|
+
const config = configFactory && tenantScopedDb
|
|
973
|
+
? configFactory({
|
|
972
974
|
user: { id: jobSystemUser.id, tenantId },
|
|
973
975
|
db: tenantScopedDb,
|
|
974
976
|
secrets: context.secrets,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ConfigAccessorFactory } from "@cosmicdrift/kumiko-types/config";
|
|
2
|
+
import type { AppContext } from "@cosmicdrift/kumiko-types/handlers";
|
|
3
|
+
export declare function configAccessorFactoryOf(context: Pick<AppContext, "configAccessorFactory" | "_configAccessorFactory">): ConfigAccessorFactory | undefined;
|
|
@@ -22,6 +22,7 @@ import { isPlainObject } from "../utils/is-plain-object.js";
|
|
|
22
22
|
import { INTERACTIVE_SIGN_IN_POLICY, resolveActiveMembershipFn } from "./active-membership.js";
|
|
23
23
|
import { appendDomainEventCore } from "./append-event-core.js";
|
|
24
24
|
import { resolveAuthClaims as runAuthClaimsResolver } from "./auth-claims-resolver.js";
|
|
25
|
+
import { configAccessorFactoryOf } from "./config-accessor-factory.js";
|
|
25
26
|
import { runBatch, unwrapSingle } from "./dispatch-batch.js";
|
|
26
27
|
import { executeQuery } from "./dispatch-query.js";
|
|
27
28
|
import { executeWrite } from "./dispatch-write.js";
|
|
@@ -393,8 +394,9 @@ readOnlyForTenant = false) {
|
|
|
393
394
|
});
|
|
394
395
|
}
|
|
395
396
|
// Mirror notify: only built when the config feature wired its factory.
|
|
396
|
-
const
|
|
397
|
-
|
|
397
|
+
const configFactory = configAccessorFactoryOf(context);
|
|
398
|
+
const config = configFactory && db
|
|
399
|
+
? configFactory({
|
|
398
400
|
user: { id: user.id, tenantId: user.tenantId },
|
|
399
401
|
db,
|
|
400
402
|
secrets: context.secrets,
|
|
@@ -404,7 +406,6 @@ readOnlyForTenant = false) {
|
|
|
404
406
|
// identity is the target tenant's system user so the caller's own user-scope
|
|
405
407
|
// values never resolve into another tenant's config; the db is tenant-mode
|
|
406
408
|
// scoped to the target.
|
|
407
|
-
const configFactory = context._configAccessorFactory;
|
|
408
409
|
const configFor = configFactory && dbSource
|
|
409
410
|
? (targetTenantId) => {
|
|
410
411
|
if (targetTenantId === user.tenantId && config)
|
|
@@ -1268,8 +1269,9 @@ function buildAuthClaimsContext(ctx, user) {
|
|
|
1268
1269
|
projectionRegistry: ctx.registry,
|
|
1269
1270
|
},
|
|
1270
1271
|
});
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1272
|
+
const authClaimsConfigFactory = configAccessorFactoryOf(context);
|
|
1273
|
+
const configAccessor = authClaimsConfigFactory
|
|
1274
|
+
? authClaimsConfigFactory({
|
|
1273
1275
|
user: { id: user.id, tenantId: user.tenantId },
|
|
1274
1276
|
db,
|
|
1275
1277
|
secrets: context.secrets,
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -41,3 +41,4 @@ export type { ProjectionStatus } from "./projection-state.ts";
|
|
|
41
41
|
export { createProjectionStateTable, ProjectionStatuses, projectionStateTable, } from "./projection-state.ts";
|
|
42
42
|
export { runProjectionsForEvent } from "./projections-runner.ts";
|
|
43
43
|
export { ACCESS_INVALIDATION_CONSUMER_NAME, createAccessInvalidationEventConsumer, createSearchEventConsumer, createSseBroadcastEventConsumer, SEARCH_CONSUMER_NAME, SSE_BROADCAST_CONSUMER_NAME, } from "./system-hooks.ts";
|
|
44
|
+
export { TENANT_TIMEZONE_CONFIG_KEY } from "./tenant-timezone-config-key.ts";
|
package/dist/pipeline/index.js
CHANGED
|
@@ -23,3 +23,4 @@ export { getAllProjectionProgress, getProjectionState, listProjectionsWithState,
|
|
|
23
23
|
export { createProjectionStateTable, ProjectionStatuses, projectionStateTable, } from "./projection-state.js";
|
|
24
24
|
export { runProjectionsForEvent } from "./projections-runner.js";
|
|
25
25
|
export { ACCESS_INVALIDATION_CONSUMER_NAME, createAccessInvalidationEventConsumer, createSearchEventConsumer, createSseBroadcastEventConsumer, SEARCH_CONSUMER_NAME, SSE_BROADCAST_CONSUMER_NAME, } from "./system-hooks.js";
|
|
26
|
+
export { TENANT_TIMEZONE_CONFIG_KEY } from "./tenant-timezone-config-key.js";
|
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.ts";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.ts";
|
|
11
11
|
export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.ts";
|
|
12
|
-
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListEmptyStateSpec, 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.ts";
|
|
12
|
+
export type { ActionFormFooterAction, ActionFormLoad, ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessFact, ActionFormSuccessLink, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldAffix, FieldCondition, FieldFillAction, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionClientAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.ts";
|
|
13
13
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.ts";
|
|
14
14
|
export type { TargetRef } from "../engine/types/target-ref.ts";
|
|
15
15
|
export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.ts";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type UpgradeChangelogManual = {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly version: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly status: "open" | "migrated" | "not-applicable";
|
|
6
|
+
};
|
|
7
|
+
export type UpgradeChangelogSection = {
|
|
8
|
+
readonly from: string;
|
|
9
|
+
readonly to: string;
|
|
10
|
+
/** YYYY-MM-DD. */
|
|
11
|
+
readonly date: string;
|
|
12
|
+
readonly breaking: readonly {
|
|
13
|
+
readonly version: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
}[];
|
|
16
|
+
readonly manual: readonly UpgradeChangelogManual[];
|
|
17
|
+
readonly codemods: readonly {
|
|
18
|
+
readonly version: string;
|
|
19
|
+
readonly codemod: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
export declare function renderUpgradeSection(section: UpgradeChangelogSection): string;
|
|
24
|
+
/** Puts the section on top; a section for the same target version is replaced, so a re-run stays idempotent. */
|
|
25
|
+
export declare function mergeUpgradeSection(existing: string | undefined, section: UpgradeChangelogSection): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const CHANGELOG_HEADER = `# Changelog
|
|
2
|
+
|
|
3
|
+
Kumiko upgrades of this app, written by \`kumiko-upgrade --apply\`. Newest first.
|
|
4
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
`;
|
|
6
|
+
const SECTION_START = /^## \[/;
|
|
7
|
+
const PACKAGES_LABEL = "@cosmicdrift/kumiko-*";
|
|
8
|
+
function printableLine(text) {
|
|
9
|
+
return text.replace(/\p{Cc}/gu, " ");
|
|
10
|
+
}
|
|
11
|
+
function bulletList(title, lines) {
|
|
12
|
+
return lines.length === 0 ? [] : ["", `### ${title}`, "", ...lines.map((line) => `- ${line}`)];
|
|
13
|
+
}
|
|
14
|
+
export function renderUpgradeSection(section) {
|
|
15
|
+
return [
|
|
16
|
+
`## [${section.to}] - ${section.date}`,
|
|
17
|
+
...bulletList("Changed", [`${PACKAGES_LABEL}: ${section.from} → ${section.to}`]),
|
|
18
|
+
...bulletList("Breaking", section.breaking.map((entry) => `${entry.version} · ${printableLine(entry.title)}`)),
|
|
19
|
+
...bulletList("Manual migrations", section.manual.map((entry) => `[${entry.status}] ${entry.id} · ${entry.version} · ${printableLine(entry.title)}`)),
|
|
20
|
+
...bulletList("Codemods applied", section.codemods.map((entry) => `${entry.version} · ${entry.codemod} · ${printableLine(entry.title)}`)),
|
|
21
|
+
"",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
function splitSections(content) {
|
|
25
|
+
const lines = content.split("\n");
|
|
26
|
+
const firstSection = lines.findIndex((line) => SECTION_START.test(line));
|
|
27
|
+
if (firstSection === -1)
|
|
28
|
+
return { header: content.replace(/\n*$/, "\n"), sections: [] };
|
|
29
|
+
const header = lines.slice(0, firstSection).join("\n").replace(/\n*$/, "\n");
|
|
30
|
+
const sections = [];
|
|
31
|
+
let current = [];
|
|
32
|
+
for (const line of lines.slice(firstSection)) {
|
|
33
|
+
if (SECTION_START.test(line) && current.length > 0) {
|
|
34
|
+
sections.push(current.join("\n").replace(/\n*$/, "\n"));
|
|
35
|
+
current = [];
|
|
36
|
+
}
|
|
37
|
+
current.push(line);
|
|
38
|
+
}
|
|
39
|
+
sections.push(current.join("\n").replace(/\n*$/, "\n"));
|
|
40
|
+
return { header, sections };
|
|
41
|
+
}
|
|
42
|
+
/** Puts the section on top; a section for the same target version is replaced, so a re-run stays idempotent. */
|
|
43
|
+
export function mergeUpgradeSection(existing, section) {
|
|
44
|
+
const { header, sections } = splitSections(existing ?? CHANGELOG_HEADER);
|
|
45
|
+
const prefix = `## [${section.to}]`;
|
|
46
|
+
const others = sections.filter((candidate) => !candidate.startsWith(prefix));
|
|
47
|
+
return [header, renderUpgradeSection(section), ...others].join("\n");
|
|
48
|
+
}
|
package/dist/upgrade-cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
4
4
|
import { getFlag, getStringFlag, parseArgs } from "./arg-parser.js";
|
|
5
5
|
import { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries, } from "./engine/index.js";
|
|
6
6
|
import { ensureTemporalPolyfill } from "./time/index.js";
|
|
7
|
+
import { mergeUpgradeSection } from "./upgrade-changelog.js";
|
|
7
8
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
8
9
|
const SEMVER_CORE_RE = /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?$/;
|
|
9
10
|
const CODEMOD_SUBDIR = "scripts/codemod";
|
|
@@ -340,12 +341,24 @@ function carryOverManual(previous, newManual) {
|
|
|
340
341
|
...(previous.resolvedManual.length > 0 && { resolvedManual: previous.resolvedManual }),
|
|
341
342
|
};
|
|
342
343
|
}
|
|
344
|
+
// The marker records every codemod ever applied, not just the last bump's, so the
|
|
345
|
+
// CI guard and a resumed run see the whole history.
|
|
346
|
+
function accumulateCodemods(previous, ran) {
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
return [...previous, ...ran].filter((entry) => {
|
|
349
|
+
const key = `${entry.version}\0${entry.codemod}`;
|
|
350
|
+
if (seen.has(key))
|
|
351
|
+
return false;
|
|
352
|
+
seen.add(key);
|
|
353
|
+
return true;
|
|
354
|
+
});
|
|
355
|
+
}
|
|
343
356
|
function writeUpgradeMarker(targetDir, marker) {
|
|
344
357
|
const dir = join(targetDir, ".kumiko");
|
|
345
358
|
mkdirSync(dir, { recursive: true });
|
|
346
359
|
writeFileSync(join(dir, "upgrade-state.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf-8");
|
|
347
360
|
}
|
|
348
|
-
const NO_CARRY = { pendingManual: [], resolvedManual: [] };
|
|
361
|
+
const NO_CARRY = { codemods: [], pendingManual: [], resolvedManual: [] };
|
|
349
362
|
function isRecord(value) {
|
|
350
363
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
351
364
|
}
|
|
@@ -427,16 +440,52 @@ function carryOf(read) {
|
|
|
427
440
|
function printable(text) {
|
|
428
441
|
return text.replace(/\p{Cc}/gu, "");
|
|
429
442
|
}
|
|
430
|
-
function
|
|
443
|
+
function manualDetailsOf(entries) {
|
|
444
|
+
return new Map(entries.map((entry) => [
|
|
445
|
+
manualEntryId(entry.version, entry.title),
|
|
446
|
+
{ detail: entry.detail, migration: entry.migration },
|
|
447
|
+
]));
|
|
448
|
+
}
|
|
449
|
+
function logOpenManual(out, open, details) {
|
|
431
450
|
// skip: no open manual migrations, nothing to report
|
|
432
451
|
if (open.length === 0)
|
|
433
452
|
return;
|
|
434
453
|
out.log(` ⚠ ${open.length} manual migration(s) still open:`);
|
|
435
454
|
for (const entry of open) {
|
|
436
455
|
out.log(` ${printable(entry.id)} · ${printable(entry.version)} · ${printable(entry.title)}`);
|
|
456
|
+
const entryDetails = details?.get(entry.id);
|
|
457
|
+
if (entryDetails?.detail)
|
|
458
|
+
out.log(` ${printable(entryDetails.detail.replace(/\s*\n\s*/g, " "))}`);
|
|
459
|
+
if (entryDetails?.migration)
|
|
460
|
+
out.log(` → Migration: ${printable(entryDetails.migration)}`);
|
|
437
461
|
}
|
|
438
462
|
out.log(' Migrate them by hand, then mark them done: kumiko-upgrade --resolve <id|version> --reason "<what you did>" [--not-applicable]');
|
|
439
463
|
}
|
|
464
|
+
function manualWithStatus(manualEntries, previous) {
|
|
465
|
+
return manualEntries.map((entry) => {
|
|
466
|
+
const id = manualEntryId(entry.version, entry.title);
|
|
467
|
+
const resolved = previous.resolvedManual.find((candidate) => candidate.id === id);
|
|
468
|
+
return {
|
|
469
|
+
id,
|
|
470
|
+
version: entry.version,
|
|
471
|
+
title: entry.title,
|
|
472
|
+
status: resolved === undefined ? "open" : resolved.resolution,
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
// Runs only for a real bump: an app that is already current keeps its CHANGELOG.md untouched.
|
|
477
|
+
function writeUpgradeChangelog(targetDir, bump) {
|
|
478
|
+
const path = join(targetDir, "CHANGELOG.md");
|
|
479
|
+
const existing = existsSync(path) ? readFileSync(path, "utf-8") : undefined;
|
|
480
|
+
writeFileSync(path, mergeUpgradeSection(existing, {
|
|
481
|
+
from: bump.from,
|
|
482
|
+
to: bump.to,
|
|
483
|
+
date: Temporal.Now.instant().toString().slice(0, 10),
|
|
484
|
+
breaking: bump.pending.filter((entry) => entry.type === "breaking"),
|
|
485
|
+
manual: manualWithStatus(bump.manualEntries, bump.previous),
|
|
486
|
+
codemods: bump.ran,
|
|
487
|
+
}), "utf-8");
|
|
488
|
+
}
|
|
440
489
|
// Records the codemods that already wrote files before a later one failed, so
|
|
441
490
|
// a re-run resumes after them instead of replaying non-idempotent codemods.
|
|
442
491
|
function writePartialMarker(out, targetDir, pending, manualEntries, failedVersion, ran, previous) {
|
|
@@ -453,7 +502,7 @@ function writePartialMarker(out, targetDir, pending, manualEntries, failedVersio
|
|
|
453
502
|
writeUpgradeMarker(targetDir, {
|
|
454
503
|
version,
|
|
455
504
|
appliedAt: Temporal.Now.instant().toString(),
|
|
456
|
-
codemods: ran,
|
|
505
|
+
codemods: accumulateCodemods(previous.codemods, ran),
|
|
457
506
|
...carryOverManual(previous, manualEntries.map(toMarkerManual)),
|
|
458
507
|
});
|
|
459
508
|
out.err(` ⚠ ${ran.length} codemod(s) already applied; wrote marker at ${version} so a re-run resumes at the failed one.`);
|
|
@@ -469,7 +518,7 @@ async function applyCodemods(out, pending, repoRoot, targetDir, dryRun,
|
|
|
469
518
|
// Installed version for the zero-pending bootstrap marker — never a
|
|
470
519
|
// `--from` filter override (that would permanently skip real pending
|
|
471
520
|
// codemods once the CI guard compares against the fake marker).
|
|
472
|
-
markerVersion, previous) {
|
|
521
|
+
markerVersion, previous, fromVersion, manualDetails) {
|
|
473
522
|
if (pending.length === 0) {
|
|
474
523
|
out.log(" ✓ Nothing new since your version.");
|
|
475
524
|
if (!dryRun) {
|
|
@@ -477,11 +526,11 @@ markerVersion, previous) {
|
|
|
477
526
|
writeUpgradeMarker(targetDir, {
|
|
478
527
|
version: markerVersion,
|
|
479
528
|
appliedAt: Temporal.Now.instant().toString(),
|
|
480
|
-
codemods:
|
|
529
|
+
codemods: previous.codemods,
|
|
481
530
|
...carried,
|
|
482
531
|
});
|
|
483
532
|
out.log(` ✓ Applied 0 codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
484
|
-
logOpenManual(out, carried.pendingManual ?? []);
|
|
533
|
+
logOpenManual(out, carried.pendingManual ?? [], manualDetails);
|
|
485
534
|
}
|
|
486
535
|
return 0;
|
|
487
536
|
}
|
|
@@ -504,11 +553,19 @@ markerVersion, previous) {
|
|
|
504
553
|
writeUpgradeMarker(targetDir, {
|
|
505
554
|
version: markerVersion,
|
|
506
555
|
appliedAt: Temporal.Now.instant().toString(),
|
|
507
|
-
codemods:
|
|
556
|
+
codemods: previous.codemods,
|
|
508
557
|
...carried,
|
|
509
558
|
});
|
|
559
|
+
writeUpgradeChangelog(targetDir, {
|
|
560
|
+
from: fromVersion,
|
|
561
|
+
to: markerVersion,
|
|
562
|
+
pending,
|
|
563
|
+
manualEntries,
|
|
564
|
+
ran: [],
|
|
565
|
+
previous,
|
|
566
|
+
});
|
|
510
567
|
out.log(` ✓ Applied 0 codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
511
|
-
logOpenManual(out, carried.pendingManual ?? []);
|
|
568
|
+
logOpenManual(out, carried.pendingManual ?? [], manualDetails);
|
|
512
569
|
}
|
|
513
570
|
return 0;
|
|
514
571
|
}
|
|
@@ -546,11 +603,19 @@ markerVersion, previous) {
|
|
|
546
603
|
writeUpgradeMarker(targetDir, {
|
|
547
604
|
version: markerVersion,
|
|
548
605
|
appliedAt: Temporal.Now.instant().toString(),
|
|
549
|
-
codemods: ran,
|
|
606
|
+
codemods: accumulateCodemods(previous.codemods, ran),
|
|
550
607
|
...carried,
|
|
551
608
|
});
|
|
609
|
+
writeUpgradeChangelog(targetDir, {
|
|
610
|
+
from: fromVersion,
|
|
611
|
+
to: markerVersion,
|
|
612
|
+
pending,
|
|
613
|
+
manualEntries,
|
|
614
|
+
ran,
|
|
615
|
+
previous,
|
|
616
|
+
});
|
|
552
617
|
out.log(` ✓ Applied ${ran.length} codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
553
|
-
logOpenManual(out, carried.pendingManual ?? []);
|
|
618
|
+
logOpenManual(out, carried.pendingManual ?? [], manualDetails);
|
|
554
619
|
return 0;
|
|
555
620
|
}
|
|
556
621
|
function parseResolveInput(args) {
|
|
@@ -751,7 +816,7 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
751
816
|
return 1;
|
|
752
817
|
}
|
|
753
818
|
out.log("");
|
|
754
|
-
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun, markerVersion ?? currentVersion, carryOf(markerRead));
|
|
819
|
+
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun, markerVersion ?? currentVersion, carryOf(markerRead), currentVersion, verbose ? manualDetailsOf(allEntries) : undefined);
|
|
755
820
|
out.log("");
|
|
756
821
|
return code;
|
|
757
822
|
}
|
|
@@ -774,7 +839,7 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
774
839
|
if (pending.length === 0) {
|
|
775
840
|
out.log(" ✓ Nothing new since your version.");
|
|
776
841
|
out.log("");
|
|
777
|
-
logOpenManual(out, openManual);
|
|
842
|
+
logOpenManual(out, openManual, verbose ? manualDetailsOf(allEntries) : undefined);
|
|
778
843
|
return 0;
|
|
779
844
|
}
|
|
780
845
|
if (breaking.length > 0) {
|
|
@@ -819,6 +884,6 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
819
884
|
out.log(" Run with --verbose for full migration details.");
|
|
820
885
|
out.log("");
|
|
821
886
|
}
|
|
822
|
-
logOpenManual(out, openManual);
|
|
887
|
+
logOpenManual(out, openManual, verbose ? manualDetailsOf(allEntries) : undefined);
|
|
823
888
|
return 0;
|
|
824
889
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.377.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>",
|
|
@@ -199,8 +199,7 @@
|
|
|
199
199
|
"./package.json": "./package.json"
|
|
200
200
|
},
|
|
201
201
|
"dependencies": {
|
|
202
|
-
"@cosmicdrift/kumiko-
|
|
203
|
-
"@cosmicdrift/kumiko-types": "0.373.0",
|
|
202
|
+
"@cosmicdrift/kumiko-types": "0.377.0",
|
|
204
203
|
"bullmq": "^5.76.7",
|
|
205
204
|
"bun-types": "^1.3.13",
|
|
206
205
|
"hono": "^4.13.1",
|
|
@@ -216,8 +215,7 @@
|
|
|
216
215
|
"zod": "^4.4.3"
|
|
217
216
|
},
|
|
218
217
|
"devDependencies": {
|
|
219
|
-
"@cosmicdrift/kumiko-
|
|
220
|
-
"@cosmicdrift/kumiko-testing": "0.373.0",
|
|
218
|
+
"@cosmicdrift/kumiko-testing": "0.377.0",
|
|
221
219
|
"bun-types": "^1.3.13",
|
|
222
220
|
"pino-pretty": "^13.1.3"
|
|
223
221
|
},
|
package/presets/biome-app.json
CHANGED
package/src/changes.json
CHANGED
|
@@ -1,4 +1,136 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.377.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "design decisions log",
|
|
6
|
+
"detail": "docs/reference/decisions.md records lasting design decisions with their reasons. It is documentation only and changes no behavior."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.377.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Env hint links to the infra deploy runbook",
|
|
12
|
+
"detail": "The boot env report ends with a link to docs/runbooks/standard-deploy-app.md in the infra repo instead of a path in kumiko-platform that no longer exists."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.377.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "Replay parser and migration splitter cover dollar quotes, RENAME, CREATE TABLE AS and DO",
|
|
18
|
+
"detail": "splitSqlStatements keeps dollar-quoted bodies whole and throws on an unterminated one. The replay parser follows RENAME TO, RENAME COLUMN, CREATE TABLE AS and DO blocks; tables with columns it cannot derive are skipped in the schema diff. The Bun.SQL wording in db/query.ts errors and logger names now says postgres-js."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.377.0",
|
|
22
|
+
"type": "improvement",
|
|
23
|
+
"title": "providerPluginContextFromHandler and providerPluginContextFromJob",
|
|
24
|
+
"detail": "Build a ProviderPluginContext from a HandlerContext or JobContext (jobs use the system user and have no dbOutsideTransaction)."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.377.0",
|
|
28
|
+
"type": "improvement",
|
|
29
|
+
"title": "hasOutsideTransactionDb and requireOutsideTransactionDb",
|
|
30
|
+
"detail": "Public helpers in the db barrel. hasOutsideTransactionDb(ctx) narrows ctx.dbOutsideTransaction to TenantDb; requireOutsideTransactionDb(ctx) returns it or throws an InternalError."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"version": "0.377.0",
|
|
34
|
+
"type": "breaking",
|
|
35
|
+
"title": "Provider contexts expose auditUserId instead of _userId",
|
|
36
|
+
"detail": "FileProviderContext, MailTransportContext, InboundMailContext and the context read by requireSecretsContext name the audit identity auditUserId. The old underscore field is gone.",
|
|
37
|
+
"migration": "Rename _userId to auditUserId wherever a provider context is built by hand (tests, custom resolvers, worker wiring). requireSecretsContext still falls back to ctx.user.id when it is unset."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.377.0",
|
|
41
|
+
"type": "improvement",
|
|
42
|
+
"title": "Boot always sets configAccessorFactory and overrides an app value",
|
|
43
|
+
"detail": "addConfigAccessorFactory writes configAccessorFactory and the _configAccessorFactory alias from the effective configResolver, replacing any app-supplied factory. The replay parser also rejects DO blocks without a dollar-quoted body and accepts tags like $do1$."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "0.377.0",
|
|
47
|
+
"type": "improvement",
|
|
48
|
+
"title": "http moved into framework",
|
|
49
|
+
"detail": "egress(), the host policy helpers and their errors are implemented in @cosmicdrift/kumiko-framework/http. @cosmicdrift/kumiko-http now only re-exports that subpath and is removed after the consumer pass of wave 4.",
|
|
50
|
+
"migration": "Run bun kumiko-upgrade --apply, or bun kumiko codemod package-merge . --write. It rewrites imports of @cosmicdrift/kumiko-http to @cosmicdrift/kumiko-framework/http and replaces the kumiko-http dependency in package.json with kumiko-framework.",
|
|
51
|
+
"codemod": "scripts/codemod/package-merge.ts"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"version": "0.377.0",
|
|
55
|
+
"type": "improvement",
|
|
56
|
+
"title": "package-merge codemod covers headless and dispatcher-live",
|
|
57
|
+
"detail": "The mapping table of the package-merge codemod gained the headless and dispatcher-live moves into kumiko-renderer.",
|
|
58
|
+
"codemod": "scripts/codemod/package-merge.ts"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"version": "0.377.0",
|
|
62
|
+
"type": "improvement",
|
|
63
|
+
"title": "package-merge codemod covers renderer-web and CSS files",
|
|
64
|
+
"detail": "The mapping table of the package-merge codemod gained the renderer-web move into kumiko-renderer/web, and the codemod now also rewrites .css files (@import, @plugin, @config). A @source path into the old package is reported for manual follow-up.",
|
|
65
|
+
"codemod": "scripts/codemod/package-merge.ts"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"version": "0.376.0",
|
|
69
|
+
"type": "improvement",
|
|
70
|
+
"title": "kumiko-upgrade writes CHANGELOG.md and shows manual entries with --verbose",
|
|
71
|
+
"detail": "--apply adds one section per bump to CHANGELOG.md in the app (changed packages, breaking titles, manual migrations with status, codemods applied); a re-run of the same bump replaces its section. --verbose prints detail and migration of the open manual entries. A second --apply keeps the codemods already recorded in the marker. The biome-app preset allows console in bin and scripts. AppConfig takes an optional includeBundled flag that the managed kumiko/schema.ts uses to compose the bundled auth features."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"version": "0.374.0",
|
|
75
|
+
"type": "improvement",
|
|
76
|
+
"title": "actionForm successLinks and resetOnSuccess",
|
|
77
|
+
"detail": "successLinks keeps the actionForm open after a successful submit and lists links resolved like redirect; boot rejects it together with redirect, with an unknown target or with a record target without idFrom. resetOnSuccess clears typed values after success. Role projection drops links to screens the role cannot reach."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"version": "0.374.0",
|
|
81
|
+
"type": "improvement",
|
|
82
|
+
"title": "clientAction row actions",
|
|
83
|
+
"detail": "RowAction gains kind \"clientAction\" with a client-registered handler name; boot rejects a name that is not <feature>:<kebab>, and the surface keys include its confirm, confirmLabel and successMessage."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"version": "0.374.0",
|
|
87
|
+
"type": "improvement",
|
|
88
|
+
"title": "Boot validation for screen-panel fallback and text panels",
|
|
89
|
+
"detail": "DashboardScreenPanel.fallback needs visibleWhen and a registered, embeddable target without its own visibleWhen. DashboardTextPanel (kind \"text\", exported with DashboardTextBlock) rejects empty blocks and empty lists; its text keys are required translation keys. Extension-selector panels qualify a short fallback ref, and role projection drops a fallback the role cannot reach."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"version": "0.374.0",
|
|
93
|
+
"type": "improvement",
|
|
94
|
+
"title": "Dashboard record binding, header actions, refresh and relative time",
|
|
95
|
+
"detail": "Dashboards take record, actions and refresh; stat panels and text params gain a relativeTime format; the surface keys include kumiko.dashboard.record.missing."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"version": "0.374.0",
|
|
99
|
+
"type": "breaking",
|
|
100
|
+
"title": "DashboardChartPanel.valueFormat only takes the currency format",
|
|
101
|
+
"detail": "DashboardValueFormat is now a union of DashboardCurrencyFormat and DashboardRelativeTimeFormat. Stat panels take both, chart panels only the currency one, and boot rejects relativeTime on a chart.",
|
|
102
|
+
"migration": "A chart panel that declares valueFormat keeps working with { kind: \"currency\", currency }. Code that types a chart's valueFormat as DashboardValueFormat should use DashboardCurrencyFormat instead; relativeTime on a chart was never meaningful and now fails boot."
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"version": "0.374.0",
|
|
106
|
+
"type": "improvement",
|
|
107
|
+
"title": "Derived list searchability, actionForm load and drawer rowClick",
|
|
108
|
+
"detail": "projectionList searchable is derived false for entity-convention lists without searchable fields (searchable true is rejected at boot), actionForm.load registers a query checked at boot, and a drawer row action may set rowClick, counted with navigate rowClick and rejected on projectionDetail header actions."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"version": "0.374.0",
|
|
112
|
+
"type": "improvement",
|
|
113
|
+
"title": "Boot checks for field affixes and fillAction",
|
|
114
|
+
"detail": "Boot fails when a fillAction query is not a registered query handler, or when a { field } affix or a fillAction params source is not a field of the same form."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"version": "0.374.0",
|
|
118
|
+
"type": "improvement",
|
|
119
|
+
"title": "pagedListSchema and pageRows for in-memory list queries",
|
|
120
|
+
"detail": "pagedListSchema(sortableFields) and pageRows(rows, request, { searchFields, defaultSort }) cover search, sort (missing values last, ties by id), offset paging and an optional total for projectionList queries over merged rows. PagedListRequest and PagedListRowValue are exported; nextCursor is always null."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"version": "0.374.0",
|
|
124
|
+
"type": "improvement",
|
|
125
|
+
"title": "DashboardProgressTone type and unlimited label key",
|
|
126
|
+
"detail": "New exported type DashboardProgressTone for progress-list row tones. The default translations gain kumiko.dashboard.progress.unlimited."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"version": "0.374.0",
|
|
130
|
+
"type": "improvement",
|
|
131
|
+
"title": "Dashboard chart panels take a y-axis unit",
|
|
132
|
+
"detail": "DashboardChartPanel.unit (i18n key) is shown once on the y axis of a timeseries chart, with or without valueFormat. The boot validator rejects unit on other chart kinds; an untranslated unit key fails the required-surface key check."
|
|
133
|
+
},
|
|
2
134
|
{
|
|
3
135
|
"version": "0.373.0",
|
|
4
136
|
"type": "improvement",
|
|
@@ -140,3 +140,28 @@ bun node_modules/@cosmicdrift/kumiko-framework/src/scripts/codemod/remove-engine
|
|
|
140
140
|
|
|
141
141
|
Runs via `bun kumiko-upgrade --apply`. Without a mode flag it writes, then formats the changed files
|
|
142
142
|
with Biome. `--check` exits 1 while any rewritable or manual site is left.
|
|
143
|
+
|
|
144
|
+
## package-merge.ts
|
|
145
|
+
|
|
146
|
+
Packages folded into another package stay as re-export shims for one wave. The codemod rewrites
|
|
147
|
+
imports, re-exports, `import("…")` and `mock.module` specifiers (including subpaths) of the old
|
|
148
|
+
package to the new one, and in `package.json` replaces the old dependency with the target package
|
|
149
|
+
(same version range, same block, skipped when the block already lists it). The moves are the rows
|
|
150
|
+
of `PACKAGE_MERGE_MAPPINGS`: `@cosmicdrift/kumiko-http` to `@cosmicdrift/kumiko-framework/http`,
|
|
151
|
+
`@cosmicdrift/kumiko-repo-manifest` to `@cosmicdrift/kumiko-guards/repo-manifest`,
|
|
152
|
+
`@cosmicdrift/kumiko-headless` to `@cosmicdrift/kumiko-renderer/headless` (subpaths keep their name),
|
|
153
|
+
`@cosmicdrift/kumiko-dispatcher-live` to `@cosmicdrift/kumiko-renderer/live`,
|
|
154
|
+
`@cosmicdrift/kumiko-renderer-web` to `@cosmicdrift/kumiko-renderer/web` (so
|
|
155
|
+
`@import "@cosmicdrift/kumiko-renderer-web/styles.css"` becomes
|
|
156
|
+
`@import "@cosmicdrift/kumiko-renderer/web/styles.css"`).
|
|
157
|
+
|
|
158
|
+
`.css` files are covered too: the specifier of `@import`, `@plugin` and `@config` is rewritten like
|
|
159
|
+
an import. A `@source` path into `node_modules/<old package>/…` has no mechanical target and is
|
|
160
|
+
reported as manual.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bun node_modules/@cosmicdrift/kumiko-framework/src/scripts/codemod/package-merge.ts <dir> [--check | --dry-run | --write] [--no-format]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Runs via `bun kumiko-upgrade --apply`. Without a mode flag it writes. `--check` exits 1 while any
|
|
167
|
+
site is left. Run your package manager's install afterwards to refresh the lockfile.
|