ember-data-to-warp-drive-codemod
v1.2.0
Published
jscodeshift codemod suite for migrating ember-data to @warp-drive/*
Maintainers
Readme
ember-data → WarpDrive Codemod Suite
jscodeshift codemod suite for migrating [email protected] to @warp-drive/*@5.8.1
Automates the bulk of a multi-phase migration that typically touches 90+ files. Each phase is an independent jscodeshift transform (or standalone script) that can be run separately.
Table of Contents
- Quick Start
- CLI Wrapper
- Post-Migration Checker
- Migration Pipeline
- Phase 0 — Deprecation Cleanup
- Phase 1 — Import Migration
- Phase 2a — Consumer Migration
- Phase 3a — Model to Schema
- Phase 3b — Schema Index
- Phase 4 — Mirror to Official
- Manual Steps After Codemods
- Testing
- Project Structure
- Technical Notes
Quick Start
# Run all default phases (0, 1, 3a, 2a, 3b):
npx ember-data-to-warp-drive-codemod --target=frontend/app
# Dry run (preview changes without writing):
npx ember-data-to-warp-drive-codemod --target=frontend/app --dry-run
# Then: manual steps (store, handlers, extensions, inverse values)--appName is auto-detected from the nearest package.json. All phases support .ts, .js, .gts, and .gjs files.
CLI Wrapper
Instead of running 6 separate commands, use the unified CLI:
# Run all default phases (0, 1, 3a, 2a, 3b):
npx ember-data-to-warp-drive-codemod --target=frontend/app
# Run specific phases:
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=0,1
# Dry run (preview changes without writing):
npx ember-data-to-warp-drive-codemod --target=frontend/app --dry-run
# Phase 4 is opt-in (only needed for mirror packages):
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=0,1,3a,2a,3b,4
# Override auto-detected app name:
npx ember-data-to-warp-drive-codemod --target=frontend/app --appName=myappCLI Options
| Option | Description | Default |
|--------|-------------|---------|
| --target | Target directory to transform | required |
| --appName | Application name for import paths | auto-detected from package.json |
| --phases | Comma-separated list of phase IDs | 0,1,3a,2a,3b |
| --dry-run | Preview changes without writing files | false |
| --extensions | File extensions to process | ts,js,gts,gjs |
| --modelsDir | Models directory override | <target>/models |
| --schemasDir | Schemas directory override | <target>/schemas |
| --baseOnlyClasses | Comma-separated base-only class names | [] |
| --verbose | Show file-level detail from jscodeshift | false |
| --quiet | Summary only, suppress per-phase output | false |
| --strict | Exit with error if any phase has errors | false |
| --json | Machine-readable JSON output (for CI) | false |
| --post-check | Run post-migration diagnostic scanner instead of codemods | false |
| --useTypeChecker | Use TypeScript type-checker to prevent false positive .get()/.set() transforms (Phase 0). CLI-only. | false |
| --tsconfig | Explicit path to tsconfig.json (used with --useTypeChecker). CLI-only. | auto-detected |
Config File (.codemodrc.json)
Create a .codemodrc.json in your working directory to avoid repeating options:
{
"appName": "myapp",
"target": "frontend/app",
"modelsDir": "frontend/app/models",
"schemasDir": "frontend/app/schemas",
"extensions": "ts,js,gts,gjs",
"baseOnlyClasses": ["CompletionDependent"]
}CLI arguments override config file values. dryRun is CLI-only (never read from config).
Validation
The CLI validates options before running:
- Fatal:
--targetmissing or directory doesn't exist,--appNamemissing when needed - Warning:
appName === 'app', phase ordering issues, missingschemasDirfor phase 3b
Post-Migration Checker
After running the codemods, scan for common issues that need manual attention:
npx ember-data-to-warp-drive-codemod --post-check --target=frontend/app
# Verbose mode (show all file locations):
npx ember-data-to-warp-drive-codemod --post-check --target=frontend/app --verbose
# Strict mode (warnings treated as failures, exits non-zero):
npx ember-data-to-warp-drive-codemod --post-check --target=frontend/app --strict
# JSON output (for CI integration):
npx ember-data-to-warp-drive-codemod --post-check --target=frontend/app --jsonChecks Performed
| # | Check | Status | What it scans |
|---|-------|--------|---------------|
| 1 | Store service exists | pass/fail | target/services/store.{ts,js,gts,gjs} |
| 2 | @warp-drive/ember/install | pass/fail | target/app.{ts,js,gts,gjs} |
| 3 | Remaining @ember-data/ imports | pass/warn | All .ts/.js/.gts/.gjs files |
| 4 | Remaining ember-data barrel imports | pass/warn | All .ts/.js/.gts/.gjs files |
| 5 | Remaining @ember/utils imports | pass/warn | All .ts/.js/.gts/.gjs files |
| 6 | Remaining @ember/array imports | pass/warn | All .ts/.js/.gts/.gjs files |
| 7 | Codemod-related TODO comments | pass/warn | All .ts/.js/.gts/.gjs files |
| 8 | inverse: null relationships | pass/warn | Schema files |
| 9 | Extension this. -> self. TODOs | pass/warn | Schema files |
| 10 | Remaining deprecated array APIs | pass/warn | .toArray(), .sortBy(), .filterBy(), .mapBy(), etc. |
| 11 | Remaining this.transitionTo/replaceWith | pass/warn | Deprecated route/controller methods |
| 12 | Remaining get()/set() usage | pass/warn | Ember computed property access + @ember/object imports |
| 13 | Remaining .setProperties() | pass/warn | Incompatible with SchemaRecord |
| 14 | Remaining adapter files | pass/warn | target/adapters/ directory |
| 15 | Remaining serializer files | pass/warn | target/serializers/ directory |
| 16 | Remaining legacy transforms | pass/warn | target/transforms/ (should be transformations/) |
| 17 | Model imports not rewritten to schemas | pass/warn | Consumer files still importing from models/ instead of schemas/ |
Migration Pipeline
YOUR EMBER APP
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 0: Deprecation Cleanup │
│ ───────────────────────────── │
│ this.get('x') → this.x │
│ .toArray() → Array.from(...) │
│ .sortBy('k') → .sort(...) │
│ isEmpty(x) → x == null || ... │
│ + 28 more transforms │
│ ~95% auto │
└──────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Phase 1: Import Migration │
│ ───────────────────────── │
│ @ember-data/model → @warp-drive/legacy/model │
│ @ember-data/store → myapp/services/store (type) │
│ import DS from 'ember-data' → individual @warp-drive imports │
│ + [Type] brand, relationship fixes, async→sync │
│ ~90% auto │
└──────────────────────────────┬───────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────────────┐
│ Phase 3a: Model→Schema │ │ Phase 2a: Consumer Migration │
│ ──────────────────────── │ │ ────────────────────────── │
│ │ │ │
│ app/models/user.ts │ │ import Store → import type Store │
│ ↓ │ │ import Model → import type Model │
│ app/schemas/user.ts │ │ .toArray() → Array.from(...) │
│ + app/models/user.ts │ │ │
│ (re-export stub) │ │ ~80% auto │
│ ~70% auto │ └────────────────────────────────────┘
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ Phase 3b: Schema Index │
│ ──────────────────────── │
│ │
│ Generates: │
│ schemas/index.ts │
│ ALL_SCHEMAS = [...] │
│ ALL_EXTENSIONS = [...] │
│ 100% auto │
└─────────────┬─────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ ✋ Manual Steps │
│ ────────────── │
│ • Create app/services/store.ts │
│ • Create request handlers │
│ • Rewrite extension bodies (this. → self.) │
│ • Fix relationship inverse values │
│ • Fix TypeScript errors │
└──────────────────────────────┬───────────────────────────────────┘
│
▼ (optional)
┌──────────────────────────────────────────────────────────────────┐
│ Phase 4: Mirror → Official │
│ ────────────────────────── │
│ @warp-drive-mirror/* → @warp-drive/* │
│ (Only if mirror packages were used as intermediate step) │
│ 100% auto │
└──────────────────────────────────────────────────────────────────┘Phase 0: Deprecation Cleanup
Remove deprecated Ember/ember-data APIs before the actual migration.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=0Before / After Examples
// ─── Property access ────────────────────────────────
this.get('name') → this.name
this.get('user.email') → this.user?.email
this.set('name', val) → this.name = val
obj.get('prop') → obj.prop
obj.set('prop', val) → obj.prop = val
get(obj, 'prop') → obj.prop // from @ember/object
set(obj, 'prop', val) → obj.prop = val // from @ember/object
obj.setProperties({ a: 1, b: 2 }) → obj.a = 1; obj.b = 2;
// ─── Array helpers ──────────────────────────────────
items.toArray() → Array.from(items)
items.sortBy('name') → Array.from(items).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
items.sortBy('last', 'first') → items.sort(/* chained ternary for each key */)
items.mapBy('name') → items.map(item => item.name)
items.filterBy('active', true) → items.filter(item => item.active === true)
items.findBy('id', 42) → items.find(item => item.id === 42)
items.uniq() → [...new Set(items)]
items.firstObject → items[0]
items.lastObject → items.at(-1)
// ─── Array mutations ────────────────────────────────
arr.pushObject(item) → arr.push(item)
arr.pushObjects(items) → arr.push(...items)
arr.removeObject(item) → const _idx = arr.indexOf(item); if (_idx !== -1) arr.splice(_idx, 1);
arr.removeObjects(items) → items.forEach(_item => { const _idx = arr.indexOf(_item); if (_idx !== -1) arr.splice(_idx, 1); })
// ─── Ember utils (import-gated) ─────────────────────
isEmpty(x) → x == null || x === '' || (Array.isArray(x) && x.length === 0)
isPresent(x) → x != null && x !== '' && (!Array.isArray(x) || x.length > 0)
isNone(x) → x == null
A(arr) → arr // from @ember/array
isArray(x) → Array.isArray(x)
// ─── Routing (Route/Controller classes only) ────────
this.transitionTo('route') → this.router.transitionTo('route')
this.replaceWith('route') → this.router.replaceWith('route')
// ─── Relationship access ────────────────────────────
model.hasMany('items').value() → model.items
model.hasMany('items').ids() → (model.items || []).map(r => r.id)
model.belongsTo('user').value() → model.user32 transforms total. Automatically removes unused @ember/utils, @ember/object, and @ember/array imports. Supports aliased imports (e.g., import { get as emberGet }).
Note:
filterBy/findByemit===(strict equality), while Ember used==(loose). A// NOTEcomment is added to flag this for review.
Phase 1: Import Migration
Rewrite ember-data imports to WarpDrive, add [Type] brand, fix relationship specs.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=1Options: --appName (auto-detected from package.json)
Before / After Examples
// ─── Import rewrites ────────────────────────────────
import Model, { attr, hasMany } from '@ember-data/model';
→ import Model, { attr, hasMany } from '@warp-drive/legacy/model';
import Store from '@ember-data/store';
→ import type Store from 'myapp/services/store';
import RESTAdapter from '@ember-data/adapter/rest';
→ import RESTAdapter from '@warp-drive/legacy/adapter/rest';
// ─── DS barrel (routed per member) ──────────────────
import DS from 'ember-data';
class Foo extends DS.Model { ... }
const { RESTAdapter } = DS;
→ import Model from '@warp-drive/legacy/model';
import RESTAdapter from '@warp-drive/legacy/adapter/rest';
class Foo extends Model { ... }
// ─── [Type] brand injection ─────────────────────────
// app/models/user.ts
class User extends Model { class User extends Model {
@attr name!: string; → declare [Type]: 'user';
} @attr name!: string;
}
// ─── Relationship type fixes ────────────────────────
@hasMany('comment') comments!: AsyncHasMany<Comment>;
→ @hasMany('comment', { async: false, inverse: null }) comments!: Comment[];
@belongsTo('user') author!: AsyncBelongsTo<User>;
→ @belongsTo('user', { async: false, inverse: null }) author!: User;
// ─── Registry module removal ────────────────────────
declare module 'ember-data/types/registries/model' { ... }
→ // (removed entirely)DS Barrel Routing Map
| DS.X Member | Target Package |
|---------------|----------------|
| Model, attr, belongsTo, hasMany | @warp-drive/legacy/model |
| RESTAdapter | @warp-drive/legacy/adapter/rest |
| JSONAPIAdapter | @warp-drive/legacy/adapter/json-api |
| JSONSerializer | @warp-drive/legacy/serializer/json |
| JSONAPISerializer | @warp-drive/legacy/serializer/json-api |
| RESTSerializer | @warp-drive/legacy/serializer/rest |
| Transform | @warp-drive/legacy/serializer/transform |
| Unknown member | TODO comment added |
[Type] brand detection works with: direct extends Model, relative model imports (../task), and mixin patterns (SortableMixin(Model)).
Phase 2a: Consumer Migration
Update consumer files (routes, controllers, components) that reference ember-data APIs.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=2aOptions: --appName (auto-detected from package.json)
Before / After Examples
// ─── Store import → type-only (when used only as type) ───
import Store from '@ember-data/store';
@service declare store: Store;
→ import type Store from 'myapp/services/store';
// ─── Model import → type-only (when used only as type) ───
import User from 'myapp/models/user';
async model(): Promise<User[]> { ... }
→ import type User from 'myapp/models/user';Type-only detection covers: annotations, type references, generics, interfaces, as expressions, mapped types, conditional types, and indexed access types.
Phase 3a: Model to Schema
Extract model field definitions into WarpDrive schema scaffolds.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=3aOptions: --appName (auto-detected) | --dryRun=true | --schemasDir=path | --baseOnlyClasses=Foo,Bar
What Happens to Each Model
app/models/user.ts app/schemas/user.ts (NEW)
┌──────────────────────┐ ┌──────────────────────────────────┐
│ import Model, { │ │ import { withDefaults } from │
│ attr, hasMany │ │ '@warp-drive/legacy/model/...' │
│ } from '...' │ │ │
│ │ │ interface UserSelf { │
│ export default class │ ────────► │ [Type]: 'user'; │
│ User extends Model │ │ name: string; │
│ { │ │ posts: Post[]; │
│ @attr name!: string│ │ } │
│ @hasMany posts!:.. │ │ │
│ @tracked isEditing │ │ export const UserSchema = │
│ │ │ withDefaults({ │
│ get displayName() {│ │ type: 'user', │
│ return this.name │ │ fields: [ │
│ } │ │ { kind: 'attribute', ... },│
│ } │ │ { kind: 'hasMany', ... }, │
│ │ │ { kind: '@local', ... }, │
│ export interface │ │ ] │
│ IUserStats { ... } │ │ }) as LegacyResourceSchema; │
└──────────────────────┘ │ │
│ export const UserExtension = { │
app/models/user.ts (REPLACED) │ get displayName() { │
┌──────────────────────┐ │ // TODO: rewrite this. → │
│ export type { │ │ // self. │
│ User as default │ │ }, │
│ } from │ │ }; │
│ 'myapp/schemas/...'│ │ │
│ │ │ export interface IUserStats {..} │
│ export type { │ └──────────────────────────────────┘
│ IUserStats │
│ } from │
│ 'myapp/schemas/...'│
└──────────────────────┘Member Classification
Model Class Member Schema Output
───────────────── ─────────────
@attr('string') name → { kind: 'attribute', name: 'name', type: 'string' }
@attr() name → { kind: 'attribute', name: 'name' }
@attr('string', { defaultValue }) → { kind: 'attribute', ... } + TODO comment
@belongsTo('user', opts) → { kind: 'belongsTo', name, type: 'user', options }
@hasMany('post', opts) → { kind: 'hasMany', name, type: 'post', options }
@tracked isEditing = false → { kind: '@local', name, options: { defaultValue: false } }
@service('store') store → Service record + TODO comment
get displayName() { ... } → Extension getter scaffold
set name(v) { ... } → Extension setter scaffold
doSomething() { ... } → Extension method scaffold
@action save() { ... } → Extension getter-closure scaffold
@cached get sorted() { ... } → Extension getter + "Was @cached" note
static modelName = 'user' → (skipped — not in extensions)
constructor() → (skipped)Phase 3b: Schema Index
Generate schemas/index.ts barrel file collecting all schemas and extensions.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=3bOutput:
// This file is auto-generated by phase-3b-schema-index.ts. Do not edit manually.
import { UserSchema, UserExtension } from './user';
import { PostSchema } from './post';
export const ALL_SCHEMAS = [UserSchema, PostSchema];
export const ALL_EXTENSIONS = [UserExtension];Phase 4: Mirror to Official
Replace @warp-drive-mirror/* with @warp-drive/*. Only needed if mirror packages were used as an intermediate step.
npx ember-data-to-warp-drive-codemod --target=frontend/app --phases=4Handles static import, require(), and dynamic import().
Manual Steps After Codemods
After running all phases, these tasks require manual work:
Must Do
- [ ] Create
app/services/store.tswithuseLegacyStore()and RequestManager pipeline - [ ] Create request handlers (
app/handlers/) for your API endpoints - [ ] Create WarpDrive transformations (
app/transformations/) for custom attribute types - [ ] Add
import '@warp-drive/ember/install'toapp/app.js - [ ] Fix relationship
inversevalues — codemods usenullas placeholder, correct values require domain knowledge - [ ] Rewrite extension bodies — change
this.propName→self.propName, replace@serviceinjections withgetService(self, 'service-name')
Should Do
- [ ] Audit
filterBy/findByfor loose vs strict equality edge cases (look for// NOTEcomments) - [ ] Handle
@attrdefaultValue TODOs at the handler/transform layer - [ ] Review extension Self interface types for accuracy
Environment
- [ ] Upgrade TypeScript to 5.x for
moduleResolution: "bundler"support - [ ] Fix TypeScript errors from WarpDrive generics (id nullability, relationship types)
Testing
npm test600+ tests across 10 test suites covering all phases, utilities, CLI wrapper, post-migration checker, and type-checker integration.
Tip: Some chained patterns like
items.filterBy('active').sortBy('name')may require running Phase 0 twice, since jscodeshift processes outer call expressions first.
Project Structure
├── src/
│ ├── utils/
│ │ ├── imports.ts addImport, removeImport, isUsedOnlyAsType
│ │ ├── decorators.ts getDecorators, classifyMember
│ │ ├── schema-builder.ts buildSchemaFile, buildModelStub
│ │ ├── ember-apis.ts Pattern matchers (isSortBy, isMapBy, ...)
│ │ ├── gts-support.ts .gts/.gjs <template> extraction
│ │ ├── type-checker.ts Optional TS type-checker for .get()/.set() guards
│ │ └── reporter.ts Phase summary + grand summary reporting
│ │
│ ├── cli.ts CLI wrapper — single command migration
│ ├── post-check.ts Post-migration diagnostic scanner
│ ├── phase-0-deprecation-cleanup.ts 32 deprecated API transforms
│ ├── phase-1-import-migration.ts Import rewrites + [Type] brand
│ ├── phase-2a-consumer-migration.ts value→type imports, toArray, sortBy
│ ├── phase-3a-model-to-schema.ts Model→Schema+Extension extraction
│ ├── phase-3b-schema-index.ts Barrel file generator (standalone)
│ └── phase-4-mirror-to-official.ts @warp-drive-mirror→@warp-drive
│
└── __tests__/
├── fixtures/ Before/after .input.ts/.output.ts pairs
├── cli.test.ts CLI wrapper + reporter tests
├── post-check.test.ts Post-migration checker tests
├── type-checker.test.ts Type-checker integration tests
├── phase-0.test.ts 96 tests
├── phase-1.test.ts 69 tests
├── phase-2a.test.ts 16 tests
├── phase-3a.test.ts 88 tests
├── phase-3b.test.ts 24 tests
├── phase-4.test.ts 16 tests
└── utils.test.ts 89 testsArchitecture
┌─────────────┐
│ ember-apis │ Pattern matchers:
│ │ isSortBy, isMapBy, isFilterBy,
│ │ isEmberGet, isEmberSet, ...
└──────┬──────┘
│ used by
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Phase 0 │ │ Phase 2a │ │ Phase 1 │
│ Deprec. │ │ Consumer │ │ Imports │
│ cleanup │ │ migration│ │ migration│
└──────────┘ └──────────┘ └─────┬────┘
│ feeds into
┌─────────────┐ │
│ decorators │ │
│ │ classifyMember, ┌──────────┐
│ │ getDecorators │ Phase 3a │
└──────┬──────┘ │ │ Model → │
│ used by └───────────►│ Schema │
└──────────────────────────────►│ │
┌─────────────┐ └──────────┘
│ schema- │ buildSchemaFile, │
│ builder │ buildModelStub │
└──────┬──────┘ │
│ used by │
└──────────────────────────────────────┘
┌─────────────┐ ┌─────────────┐
│ imports │ │ gts-support │
│ │ │ │
│ addImport, │ │ withGts- │
│ removeImport│ │ Support() │
└──────┬──────┘ └──────┬───────┘
│ used by all │ wraps all
└────────────────────┘ phase transformsTechnical Notes
- Parser:
@babel/parserwith TypeScript support in "legacy" decorator mode (default for--parser=ts) - Decorator access: Uses
ClassProperty.decoratorsdirectly — workaround for jscodeshift#469 whereroot.find(j.Decorator)misses decorators on ClassProperty nodes - Bare decorators: Handles
@Foo.bar(MemberExpression without call parens)
| Feature | Description |
|---------|-------------|
| False positive guards | obj.get() / obj.set() skip non-Ember receivers (Map, URLSearchParams, Headers, FormData, etc.) and new expressions. .toArray() skips new expression receivers. Optional --useTypeChecker flag uses the TypeScript compiler API for type-aware detection (catches arbitrarily-named variables like const fd = new FormData(); fd.get('x')). |
| Side-effect safety | isEmpty(), isPresent(), removeObject, removeObjects, setProperties detect side-effectful arguments and wrap in IIFEs or hoist to temp variables. |
| Invalid identifiers | Property names like some-prop use computed access (this['some-prop']). sortBy/mapBy/filterBy/findBy skip transforms when key is not a valid JS identifier. |
| Return values | pushObject preserves return value (the item) via comma operator. removeObject/removeObjects/pushObjects return the array in expression context. |
| Aliased imports | import { get as emberGet } — resolves to local name before matching. |
| Scoped transforms | transitionTo/replaceWith only transform inside Route/Controller classes. Works with both ClassDeclaration and ClassExpression. |
| Import-gated | A(), isArray(), isEmpty, isPresent, isNone, get, set only transform when imported from their respective Ember packages. Import removal is deferred. |
- Extension bodies: Copied as raw source text with TODO comments —
this.→self.rewriting has too many unbounded patterns to automate safely - Relationship inverses: Set to
nullas placeholder — correct values require domain knowledge - Relationship options: Serialized as JS object literals with unquoted keys (
{ async: false, inverse: null }). Complex values (identifiers, arrays, spreads) emit TODO comments. @attrdefaultValue: Emits a TODO comment preserving the original options@cachedgetters: Annotated with// NOTE: Was @cached in original model- Static members / constructor: Excluded from extension scaffolds
- Private properties: Both
_underscoreconvention and#privatefields classified correctly - Named exports: Types/interfaces →
export type { }. Enums/consts/functions/classes →export { }(value re-exports). [Type]brand: Self interfaces include[Type]: 'model-name'to satisfyWithLegacy<T extends TypedRecordInstance>- Field kinds: All
@attrusekind: 'attribute'.@localfields placed inline inwithDefaults(). - Registration TODOs:
store.schema.registerResource(Schema); registerDerivations(store.schema); store.schema.CAUTION_MEGA_DANGER_ZONE_registerExtension(Extension);
- Type-only detection: Covers type annotations, type references, generics, interfaces,
asexpressions, mapped types, conditional types, indexed access types - Import merging:
addImportchecks all declarations from the same source and respectsimportKind— value specifiers are never merged into type-only imports - Store imports: Default import becomes type-only when used exclusively in type positions. Named value exports kept as value imports with TODO.
- Uses
content-tagto extract<template>blocks, replace with valid JS placeholders, run transforms, then restore - All phases wrapped via
withGtsSupport() - Placeholder restoration validated — warns if any placeholders survive in output
- Dollar signs in template content are safe (Glimmer uses
{{...}}not${...})
| Import | Package |
|--------|---------|
| withDefaults | @warp-drive/legacy/model/migration-support |
| LegacyResourceSchema | @warp-drive/core/types/schema/fields |
| CAUTION_MEGA_DANGER_ZONE_Extension (type) | @warp-drive/core/reactive |
| Type | @warp-drive/core/types/symbols |
| WithLegacy | @warp-drive/legacy/model/migration-support |
