From 9c4962bc9d249d0107fb5b7613bddc1d9f6ec5de Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 21 Nov 2022 19:19:54 -0700 Subject: [PATCH 1/9] Avoid exposing `OWNER` from `@glimmer/owner` There are pre-existing runtime shenanigans in `@ember/object/internals` to add debug-only errors to the class in dev builds. Those runtime shenanigans produce the need for type-level shenanigans to match: TS gets stuck here because the runtime shenanigans declare `FrameworkObject` with a class expression (rather than the usual class declaration form). That in turn means TS needs to be able to fully name the type produced by the clsas expression, which includes the `OWNER` symbol from `@glimmer/owner`. By explicitly giving the declaration a type when assigning it the class expression, instead of relying on inference, TS no longer needs to name the `OWNER` property key from the super class, eliminating the private name shenanigans. Co-authored-by: Dan Freeman --- packages/@ember/object/-internals.ts | 16 +++++++++++++++- packages/@ember/object/core.ts | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/@ember/object/-internals.ts b/packages/@ember/object/-internals.ts index d38dbca73e3..2cdc10e94fd 100644 --- a/packages/@ember/object/-internals.ts +++ b/packages/@ember/object/-internals.ts @@ -7,9 +7,23 @@ import { symbol } from '@ember/-internals/utils'; import { DEBUG } from '@glimmer/env'; import EmberObject from '.'; +// Here we have runtime shenanigans to add debug-only errors to the class in dev +// builds. Those runtime shenanigans produce the need for type-level shenanigans +// to match: if we just assign without an explicit type annotation on the `let` +// binding below for `FrameworkObject`, TS gets stuck because this creates +// `FrameworkObject` with a class expression (rather than the usual class +// declaration form). That in turn means TS needs to be able to fully name the +// type produced by the clsas expression, which includes the `OWNER` symbol from +// `@glimmer/owner`. +// +// By explicitly giving the declaration a type when assigning it the class +// expression, instead of relying on inference, TS no longer needs to name the +// `OWNER` property key from the super class, eliminating the private name +// shenanigans. + // eslint-disable-next-line @typescript-eslint/no-empty-interface interface FrameworkObject extends EmberObject {} -let FrameworkObject = class FrameworkObject extends EmberObject {}; +let FrameworkObject: typeof EmberObject = class FrameworkObject extends EmberObject {}; if (DEBUG) { const INIT_WAS_CALLED = Symbol('INIT_WAS_CALLED'); diff --git a/packages/@ember/object/core.ts b/packages/@ember/object/core.ts index 773c07ca770..7101c70a478 100644 --- a/packages/@ember/object/core.ts +++ b/packages/@ember/object/core.ts @@ -234,6 +234,7 @@ interface CoreObject { _super(...args: any[]): any; } class CoreObject { + /** @internal */ [OWNER]?: Owner; constructor(owner?: Owner) { From 4e62cd659f296361ad76de47ebc063e938160db0 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 21 Nov 2022 19:23:02 -0700 Subject: [PATCH 2/9] Refactor tsconfigs to share compiler options This lets each config specify *only* how it actually does a build (or not!), while sharing the config explicitly. It also fixes an existing bug in the compilation settings which was not affecting *normal* TS compilation with `tsc`, but was generating noise in the `broccoli-typescript-compiler` pipeline because the packages happened to be resolved in a different order such that the `loader` "package" definition, and its declaration of the `require` module, was not present in the graph. Set it explicitly in `compilerOptiosn.paths` to fix that. --- .gitattributes | 5 ++++- tsconfig.json | 34 ++------------------------------- tsconfig/compiler-options.json | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 33 deletions(-) create mode 100644 tsconfig/compiler-options.json diff --git a/.gitattributes b/.gitattributes index 09bc62c1393..9e09e498870 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ # Set the default behavior, in case people don't have core.autocrlf set. -* text=auto \ No newline at end of file +* text=auto + +# Tell GH to render all tsconfigs in the tsconfig dir correctly. +tsconfig/*.json linguist-language=JSON-with-Comments diff --git a/tsconfig.json b/tsconfig.json index 7b0a17746e1..fb94bf9d432 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,39 +1,9 @@ { + "extends": "./tsconfig/compiler-options.json", "compilerOptions": { - // Compilation Configuration - "target": "es2017", - "sourceMap": true, "outDir": "dist", - "baseUrl": "packages", - "rootDir": "packages", - - // Environment Configuration - "experimentalDecorators": true, - "moduleResolution": "node", - - // Enhance Strictness - "strict": true, - "noImplicitReturns": true, - "allowUnreachableCode": false, - "noPropertyAccessFromIndexSignature": true, - "noUnusedLocals": true, - "noUncheckedIndexedAccess": true, - "noUnusedParameters": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": false, - - "newLine": "LF", - "noEmit": true, - - "allowJs": true, - "checkJs": false, - - "paths": { - "backburner": ["../node_modules/backburner.js/dist/backburner.d.ts"] - } + "noEmit": true }, - "include": ["packages/**/*.ts"], - "exclude": ["dist", "node_modules", "tmp", "types"] } diff --git a/tsconfig/compiler-options.json b/tsconfig/compiler-options.json new file mode 100644 index 00000000000..8d90d764ed0 --- /dev/null +++ b/tsconfig/compiler-options.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + // Compilation Configuration + "target": "es2019", + "sourceMap": true, + "baseUrl": "../packages", + "rootDir": "../packages", + + // Environment Configuration + "experimentalDecorators": true, + "moduleResolution": "node", + + // Enhance Strictness + "strict": true, + "noImplicitReturns": true, + "allowUnreachableCode": false, + "noPropertyAccessFromIndexSignature": true, + "noUnusedLocals": true, + "noUncheckedIndexedAccess": true, + "noUnusedParameters": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": false, + + "newLine": "LF", + + "allowJs": true, + "checkJs": false, + + "paths": { + "backburner": ["../node_modules/backburner.js/dist/backburner.d.ts"], + "require": ["./loader/lib/index.d.ts"] + } + } +} From 845b60e84d06ba123064c9b451ee8614060d2c93 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 21 Nov 2022 19:30:33 -0700 Subject: [PATCH 3/9] Introduce a script to publish stable types Provide a script which runs the compiler against a new tsconfig for generating types, wraps all the generated modules in `declare module` statements, and then creates an `index.d.ts` which uses side-effect style imports to expose them all, just the same as the preview types but generated from source. Critically, this infrastructure does not expose *any* stable types in and of itself. Instead, it introduces a list of all the types still in preview mode which acts as a filter, and currently *all* modules are in that filter. Stabilizing the types for a given module will mean removing modules from that list and removing the corresponding preview types definitions. --- package.json | 2 + tsconfig/publish-types.json | 31 +++ types/publish.mjs | 443 ++++++++++++++++++++++++++++++++++++ types/stable/index.d.ts | 12 - yarn.lock | 12 + 5 files changed, 488 insertions(+), 12 deletions(-) create mode 100644 tsconfig/publish-types.json create mode 100755 types/publish.mjs delete mode 100644 types/stable/index.d.ts diff --git a/package.json b/package.json index 4998f01c8eb..79526e622bb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "scripts": { "build": "ember build --environment production", "docs": "ember ember-cli-yuidoc", + "types": "tsc --project tsconfig/publish-types.json && node types/publish.mjs", "link:glimmer": "node bin/yarn-link-glimmer.js", "start": "ember serve", "lint": "npm-run-all --continue-on-error --aggregate-output --parallel \"lint:!(fix)\"", @@ -143,6 +144,7 @@ "glob": "^8.0.3", "html-differ": "^1.4.0", "lodash.uniq": "^4.5.0", + "magic-string": "^0.26.7", "mkdirp": "^1.0.4", "mocha": "^9.2.2", "npm-run-all": "^4.1.5", diff --git a/tsconfig/publish-types.json b/tsconfig/publish-types.json new file mode 100644 index 00000000000..3e459bb6039 --- /dev/null +++ b/tsconfig/publish-types.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./compiler-options.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + // It'd be really nice to be able to supply sourcemaps, and at some point we + // will be able to by using e.g. rollup-plugin-ts once we solve other issues + // blocking that in our publishing pipeline by way of circular and repeated + // dependencies in the graph, but at the moment we are using `magic-string` + // to post-process these to add `declare module` declarations (see the + // `types/publish.mjs` script or details), and that doesn't support updating + // *existing* source maps, unfortunately. + "declarationMap": false, + "declarationDir": "../types/stable" + }, + "include": [ + // Note: these will also pull on all their transitive dependencies, so we + // will end up publishing the (private!) types for packages not named here + // until we update the actual internals to avoid referencing them! + "../packages/@ember/**/*", + "../packages/ember/**/*", + "../packages/@glimmer/**/*" + ], + "exclude": [ + "../**/type-tests", + "../**/tests", + "../**/internal-test-helpers" + ] +} diff --git a/types/publish.mjs b/types/publish.mjs new file mode 100755 index 00000000000..7bfb209bd96 --- /dev/null +++ b/types/publish.mjs @@ -0,0 +1,443 @@ +#!/usr/bin/env node +// @ts-check + +/** + This script is used to publish Ember's type definitions. The basic workflow + is: + + 1. Run `tsc` against the Ember packages which make up its public API, with + the output being `/types/stable`. + + 2. Wrap each emitted module in a `declare module` statement. While doing so, + keep track of the full list of emitted modules. + + 3. Check that each module emitted is included in `types/stable/index.d.ts`, + if and only if it also appears in a list of stable types modules defined + in this script, so that they all "show up" to end users. That list will + eventually be the list of *all* modules, but this allows us to publish + iteratively as we gain confidence in the stability of the types. + + This is *not* an optimal long-term publishing strategy. We would prefer to + generate per-package roll-ups, using a Rollup plugin or some such, but we are + currently blocked on a number of internal circular dependencies as well as + the difficulty of avoiding multiple definitions of the same types reused + across many rollups. + + @packageDocumentation + */ + +import glob from 'glob'; +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import MagicString from 'magic-string'; + +/** + Modules we know we are not ready to expose yet, mostly because they do not + have enough annotations on their internals to make the generated types clear + about what is public and what is private. + + Notably, the modules will still be published, but they won't be visible to + consumers because the only way they *become* visible is by being included in + the set of type-only side effect imports, which excludes exactly these + modules. + */ +const PREVIEW_MODULES = [ + './@ember/-internals/bootstrap/index.d.ts', + './@ember/-internals/browser-environment/index.d.ts', + './@ember/-internals/browser-environment/lib/has-dom.d.ts', + './@ember/-internals/container/index.d.ts', + './@ember/-internals/container/lib/container.d.ts', + './@ember/-internals/container/lib/registry.d.ts', + './@ember/-internals/environment/index.d.ts', + './@ember/-internals/environment/lib/context.d.ts', + './@ember/-internals/environment/lib/env.d.ts', + './@ember/-internals/environment/lib/global.d.ts', + './@ember/-internals/error-handling/index.d.ts', + './@ember/-internals/glimmer/index.d.ts', + './@ember/-internals/glimmer/lib/component-managers/curly.d.ts', + './@ember/-internals/glimmer/lib/component-managers/mount.d.ts', + './@ember/-internals/glimmer/lib/component-managers/outlet.d.ts', + './@ember/-internals/glimmer/lib/component-managers/root.d.ts', + './@ember/-internals/glimmer/lib/component.d.ts', + './@ember/-internals/glimmer/lib/components/abstract-input.d.ts', + './@ember/-internals/glimmer/lib/components/input.d.ts', + './@ember/-internals/glimmer/lib/components/internal.d.ts', + './@ember/-internals/glimmer/lib/components/link-to.d.ts', + './@ember/-internals/glimmer/lib/components/textarea.d.ts', + './@ember/-internals/glimmer/lib/dom.d.ts', + './@ember/-internals/glimmer/lib/environment.d.ts', + './@ember/-internals/glimmer/lib/glimmer-component-docs.d.ts', + './@ember/-internals/glimmer/lib/glimmer-tracking-docs.d.ts', + './@ember/-internals/glimmer/lib/helper.d.ts', + './@ember/-internals/glimmer/lib/helpers/-disallow-dynamic-resolution.d.ts', + './@ember/-internals/glimmer/lib/helpers/-in-element-null-check.d.ts', + './@ember/-internals/glimmer/lib/helpers/-normalize-class.d.ts', + './@ember/-internals/glimmer/lib/helpers/-resolve.d.ts', + './@ember/-internals/glimmer/lib/helpers/-track-array.d.ts', + './@ember/-internals/glimmer/lib/helpers/action.d.ts', + './@ember/-internals/glimmer/lib/helpers/array.d.ts', + './@ember/-internals/glimmer/lib/helpers/component.d.ts', + './@ember/-internals/glimmer/lib/helpers/concat.d.ts', + './@ember/-internals/glimmer/lib/helpers/each-in.d.ts', + './@ember/-internals/glimmer/lib/helpers/fn.d.ts', + './@ember/-internals/glimmer/lib/helpers/get.d.ts', + './@ember/-internals/glimmer/lib/helpers/hash.d.ts', + './@ember/-internals/glimmer/lib/helpers/helper.d.ts', + './@ember/-internals/glimmer/lib/helpers/if-unless.d.ts', + './@ember/-internals/glimmer/lib/helpers/internal-helper.d.ts', + './@ember/-internals/glimmer/lib/helpers/log.d.ts', + './@ember/-internals/glimmer/lib/helpers/modifier.d.ts', + './@ember/-internals/glimmer/lib/helpers/mut.d.ts', + './@ember/-internals/glimmer/lib/helpers/page-title.d.ts', + './@ember/-internals/glimmer/lib/helpers/readonly.d.ts', + './@ember/-internals/glimmer/lib/helpers/unbound.d.ts', + './@ember/-internals/glimmer/lib/helpers/unique-id.d.ts', + './@ember/-internals/glimmer/lib/modifiers/action.d.ts', + './@ember/-internals/glimmer/lib/modifiers/internal.d.ts', + './@ember/-internals/glimmer/lib/modifiers/on.d.ts', + './@ember/-internals/glimmer/lib/renderer.d.ts', + './@ember/-internals/glimmer/lib/resolver.d.ts', + './@ember/-internals/glimmer/lib/setup-registry.d.ts', + './@ember/-internals/glimmer/lib/syntax/in-element.d.ts', + './@ember/-internals/glimmer/lib/syntax/let.d.ts', + './@ember/-internals/glimmer/lib/syntax/mount.d.ts', + './@ember/-internals/glimmer/lib/syntax/outlet.d.ts', + './@ember/-internals/glimmer/lib/syntax/utils.d.ts', + './@ember/-internals/glimmer/lib/template_registry.d.ts', + './@ember/-internals/glimmer/lib/template.d.ts', + './@ember/-internals/glimmer/lib/utils/bindings.d.ts', + './@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.d.ts', + './@ember/-internals/glimmer/lib/utils/debug-render-message.d.ts', + './@ember/-internals/glimmer/lib/utils/iterator.d.ts', + './@ember/-internals/glimmer/lib/utils/managers.d.ts', + './@ember/-internals/glimmer/lib/utils/outlet.d.ts', + './@ember/-internals/glimmer/lib/utils/process-args.d.ts', + './@ember/-internals/glimmer/lib/utils/serialization-first-node-helpers.d.ts', + './@ember/-internals/glimmer/lib/utils/string.d.ts', + './@ember/-internals/glimmer/lib/utils/to-bool.d.ts', + './@ember/-internals/glimmer/lib/views/outlet.d.ts', + './@ember/-internals/meta/index.d.ts', + './@ember/-internals/meta/lib/meta.d.ts', + './@ember/-internals/metal/index.d.ts', + './@ember/-internals/metal/lib/alias.d.ts', + './@ember/-internals/metal/lib/array_events.d.ts', + './@ember/-internals/metal/lib/array.d.ts', + './@ember/-internals/metal/lib/cache.d.ts', + './@ember/-internals/metal/lib/cached.d.ts', + './@ember/-internals/metal/lib/chain-tags.d.ts', + './@ember/-internals/metal/lib/change_event.d.ts', + './@ember/-internals/metal/lib/computed_cache.d.ts', + './@ember/-internals/metal/lib/computed.d.ts', + './@ember/-internals/metal/lib/decorator.d.ts', + './@ember/-internals/metal/lib/dependent_keys.d.ts', + './@ember/-internals/metal/lib/deprecate_property.d.ts', + './@ember/-internals/metal/lib/each_proxy_events.d.ts', + './@ember/-internals/metal/lib/events.d.ts', + './@ember/-internals/metal/lib/expand_properties.d.ts', + './@ember/-internals/metal/lib/get_properties.d.ts', + './@ember/-internals/metal/lib/injected_property.d.ts', + './@ember/-internals/metal/lib/libraries.d.ts', + './@ember/-internals/metal/lib/namespace_search.d.ts', + './@ember/-internals/metal/lib/observer.d.ts', + './@ember/-internals/metal/lib/path_cache.d.ts', + './@ember/-internals/metal/lib/properties.d.ts', + './@ember/-internals/metal/lib/property_events.d.ts', + './@ember/-internals/metal/lib/property_get.d.ts', + './@ember/-internals/metal/lib/property_set.d.ts', + './@ember/-internals/metal/lib/set_properties.d.ts', + './@ember/-internals/metal/lib/tags.d.ts', + './@ember/-internals/metal/lib/tracked.d.ts', + './@ember/-internals/overrides/index.d.ts', + './@ember/-internals/owner/index.d.ts', + './@ember/-internals/routing/index.d.ts', + './@ember/-internals/runtime/index.d.ts', + './@ember/-internals/runtime/lib/ext/rsvp.d.ts', + './@ember/-internals/runtime/lib/mixins/-proxy.d.ts', + './@ember/-internals/runtime/lib/mixins/action_handler.d.ts', + './@ember/-internals/runtime/lib/mixins/comparable.d.ts', + './@ember/-internals/runtime/lib/mixins/container_proxy.d.ts', + './@ember/-internals/runtime/lib/mixins/registry_proxy.d.ts', + './@ember/-internals/runtime/lib/mixins/target_action_support.d.ts', + './@ember/-internals/utils/index.d.ts', + './@ember/-internals/utils/lib/cache.d.ts', + './@ember/-internals/utils/lib/dictionary.d.ts', + './@ember/-internals/utils/lib/ember-array.d.ts', + './@ember/-internals/utils/lib/get-debug-name.d.ts', + './@ember/-internals/utils/lib/guid.d.ts', + './@ember/-internals/utils/lib/inspect.d.ts', + './@ember/-internals/utils/lib/intern.d.ts', + './@ember/-internals/utils/lib/invoke.d.ts', + './@ember/-internals/utils/lib/is_proxy.d.ts', + './@ember/-internals/utils/lib/lookup-descriptor.d.ts', + './@ember/-internals/utils/lib/make-array.d.ts', + './@ember/-internals/utils/lib/mandatory-setter.d.ts', + './@ember/-internals/utils/lib/name.d.ts', + './@ember/-internals/utils/lib/spec.d.ts', + './@ember/-internals/utils/lib/super.d.ts', + './@ember/-internals/utils/lib/symbol.d.ts', + './@ember/-internals/utils/lib/to-string.d.ts', + './@ember/-internals/utils/types.d.ts', + './@ember/-internals/views/index.d.ts', + './@ember/-internals/views/lib/compat/attrs.d.ts', + './@ember/-internals/views/lib/compat/fallback-view-registry.d.ts', + './@ember/-internals/views/lib/component_lookup.d.ts', + './@ember/-internals/views/lib/mixins/action_support.d.ts', + './@ember/-internals/views/lib/mixins/child_views_support.d.ts', + './@ember/-internals/views/lib/mixins/class_names_support.d.ts', + './@ember/-internals/views/lib/mixins/view_state_support.d.ts', + './@ember/-internals/views/lib/mixins/view_support.d.ts', + './@ember/-internals/views/lib/system/action_manager.d.ts', + './@ember/-internals/views/lib/system/event_dispatcher.d.ts', + './@ember/-internals/views/lib/system/utils.d.ts', + './@ember/-internals/views/lib/views/states.d.ts', + './@ember/-internals/views/lib/views/states/default.d.ts', + './@ember/-internals/views/lib/views/states/destroying.d.ts', + './@ember/-internals/views/lib/views/states/has_element.d.ts', + './@ember/-internals/views/lib/views/states/in_dom.d.ts', + './@ember/-internals/views/lib/views/states/pre_render.d.ts', + './@ember/application/index.d.ts', + './@ember/application/instance.d.ts', + './@ember/application/lib/lazy_load.d.ts', + './@ember/application/namespace.d.ts', + './@ember/array/index.d.ts', + './@ember/array/mutable.d.ts', + './@ember/array/proxy.d.ts', + './@ember/canary-features/index.d.ts', + './@ember/component/helper.d.ts', + './@ember/component/index.d.ts', + './@ember/component/template-only.d.ts', + './@ember/debug/container-debug-adapter.d.ts', + './@ember/debug/data-adapter.d.ts', + './@ember/debug/index.d.ts', + './@ember/debug/lib/capture-render-tree.d.ts', + './@ember/debug/lib/deprecate.d.ts', + './@ember/debug/lib/handlers.d.ts', + './@ember/debug/lib/testing.d.ts', + './@ember/debug/lib/warn.d.ts', + './@ember/deprecated-features/index.d.ts', + './@ember/destroyable/index.d.ts', + './@ember/engine/index.d.ts', + './@ember/engine/instance.d.ts', + './@ember/engine/lib/engine-parent.d.ts', + './@ember/enumerable/index.d.ts', + './@ember/enumerable/mutable.d.ts', + './@ember/error/index.d.ts', + './@ember/helper/index.d.ts', + './@ember/instrumentation/index.d.ts', + './@ember/modifier/index.d.ts', + './@ember/object/compat.d.ts', + './@ember/object/computed.d.ts', + './@ember/object/core.d.ts', + './@ember/object/evented.d.ts', + './@ember/object/events.d.ts', + './@ember/object/index.d.ts', + './@ember/object/internals.d.ts', + './@ember/object/lib/computed/computed_macros.d.ts', + './@ember/object/lib/computed/reduce_computed_macros.d.ts', + './@ember/object/mixin.d.ts', + './@ember/object/observable.d.ts', + './@ember/object/observers.d.ts', + './@ember/object/promise-proxy-mixin.d.ts', + './@ember/object/proxy.d.ts', + './@ember/owner/index.d.ts', + './@ember/polyfills/index.d.ts', + './@ember/polyfills/lib/assign.d.ts', + './@ember/renderer/index.d.ts', + './@ember/routing/-internals.d.ts', + './@ember/routing/auto-location.d.ts', + './@ember/routing/hash-location.d.ts', + './@ember/routing/history-location.d.ts', + './@ember/routing/index.d.ts', + './@ember/routing/lib/cache.d.ts', + './@ember/routing/lib/controller_for.d.ts', + './@ember/routing/lib/dsl.d.ts', + './@ember/routing/lib/engines.d.ts', + './@ember/routing/lib/generate_controller.d.ts', + './@ember/routing/lib/location-utils.d.ts', + './@ember/routing/lib/query_params.d.ts', + './@ember/routing/lib/route-info.d.ts', + './@ember/routing/lib/router_state.d.ts', + './@ember/routing/lib/routing-service.d.ts', + './@ember/routing/lib/transition.d.ts', + './@ember/routing/lib/utils.d.ts', + './@ember/routing/location.d.ts', + './@ember/routing/none-location.d.ts', + './@ember/routing/route-info.d.ts', + './@ember/routing/route.d.ts', + './@ember/routing/router-service.d.ts', + './@ember/routing/router.d.ts', + './@ember/routing/transition.d.ts', + './@ember/runloop/index.d.ts', + './@ember/service/index.d.ts', + './@ember/string/index.d.ts', + './@ember/string/lib/string_registry.d.ts', + './@ember/template-compilation/index.d.ts', + './@ember/template-factory/index.d.ts', + './@ember/template/index.d.ts', + './@ember/test/adapter.d.ts', + './@ember/test/index.d.ts', + './@ember/utils/index.d.ts', + './@ember/utils/lib/compare.d.ts', + './@ember/utils/lib/is_blank.d.ts', + './@ember/utils/lib/is_empty.d.ts', + './@ember/utils/lib/is_none.d.ts', + './@ember/utils/lib/is_present.d.ts', + './@ember/utils/lib/is-equal.d.ts', + './@ember/utils/lib/type-of.d.ts', + './@ember/version/index.d.ts', + './@glimmer/tracking/index.d.ts', + './@glimmer/tracking/primitives/cache.d.ts', + './ember-template-compiler/index.d.ts', + './ember-template-compiler/lib/plugins/assert-against-attrs.d.ts', + './ember-template-compiler/lib/plugins/assert-against-named-outlets.d.ts', + './ember-template-compiler/lib/plugins/assert-input-helper-without-block.d.ts', + './ember-template-compiler/lib/plugins/assert-reserved-named-arguments.d.ts', + './ember-template-compiler/lib/plugins/assert-splattribute-expression.d.ts', + './ember-template-compiler/lib/plugins/index.d.ts', + './ember-template-compiler/lib/plugins/transform-action-syntax.d.ts', + './ember-template-compiler/lib/plugins/transform-each-in-into-each.d.ts', + './ember-template-compiler/lib/plugins/transform-each-track-array.d.ts', + './ember-template-compiler/lib/plugins/transform-in-element.d.ts', + './ember-template-compiler/lib/plugins/transform-quoted-bindings-into-just-bindings.d.ts', + './ember-template-compiler/lib/plugins/transform-resolutions.d.ts', + './ember-template-compiler/lib/plugins/transform-wrap-mount-and-outlet.d.ts', + './ember-template-compiler/lib/plugins/utils.d.ts', + './ember-template-compiler/lib/system/bootstrap.d.ts', + './ember-template-compiler/lib/system/calculate-location-display.d.ts', + './ember-template-compiler/lib/system/compile-options.d.ts', + './ember-template-compiler/lib/system/compile.d.ts', + './ember-template-compiler/lib/system/dasherize-component-name.d.ts', + './ember-template-compiler/lib/system/initializer.d.ts', + './ember-template-compiler/lib/system/precompile.d.ts', + './ember-testing/index.d.ts', + './ember-testing/lib/adapters/adapter.d.ts', + './ember-testing/lib/adapters/qunit.d.ts', + './ember-testing/lib/ext/application.d.ts', + './ember-testing/lib/ext/rsvp.d.ts', + './ember-testing/lib/helpers.d.ts', + './ember-testing/lib/helpers/and_then.d.ts', + './ember-testing/lib/helpers/current_path.d.ts', + './ember-testing/lib/helpers/current_route_name.d.ts', + './ember-testing/lib/helpers/current_url.d.ts', + './ember-testing/lib/helpers/pause_test.d.ts', + './ember-testing/lib/helpers/visit.d.ts', + './ember-testing/lib/helpers/wait.d.ts', + './ember-testing/lib/initializers.d.ts', + './ember-testing/lib/setup_for_testing.d.ts', + './ember-testing/lib/test.d.ts', + './ember-testing/lib/test/adapter.d.ts', + './ember-testing/lib/test/helpers.d.ts', + './ember-testing/lib/test/on_inject_helpers.d.ts', + './ember-testing/lib/test/pending_requests.d.ts', + './ember-testing/lib/test/promise.d.ts', + './ember-testing/lib/test/run.d.ts', + './ember-testing/lib/test/waiters.d.ts', + './ember/index.d.ts', +]; + +const MODULES_PLACEHOLDER = '~~~MODULES GO HERE~~~'; + +const BASE_INDEX_D_TS = `\ +/** + Provides stable type definitions for Ember.js. It is generated automatically + as part of Ember's publishing process and should never be edited manually. + + To use these type definitions, add this import to any TypeScript file in your + Ember app or addon: + + \`\`\`ts + import 'ember-source/types'; + import 'ember-source/types/preview'; + \`\`\` + + @module + */ + +// This works because each of these modules presents \`declare module\` definition +// of the module and *only* that, so importing this file in turn makes those +// module declarations "visible" automatically throughout a consuming project. +// Combined with use of \`typesVersions\` (or, in the future, possibly \`exports\`) +// in \`package.json\`, this allows users to import the types without knowing the +// exact layout details. +// +// Somewhat annoyingly, every single module in the graph must appear here. For +// now, while we are publishing ambient types, that means we must maintain this +// by hand. When we start emitting types from the source, we will need to do the +// same work, but automatically. + +// STATUS NOTE: this does not yet include Ember's full public API, only the +// subset of it for which we have determined +// +// Over time, it will come to include *all* of Ember's types, and the matching +// \`preview\` types will become empty. This is means that someone who writes the +// import we recommend-- +// +// \`\`\`ts +// import 'ember-source/types'; +// import 'ember-source/types/preview'; +// \`\`\` +// +// --will always get the most up-to-date mix of preview and stable types, with +// no extra effort required. + +${MODULES_PLACEHOLDER} +`; + +const TYPES_DIR = path.join('types', 'stable'); + +async function main() { + fs.rmSync(TYPES_DIR, { recursive: true, force: true }); + fs.mkdirSync(TYPES_DIR, { recursive: true }); + + spawnSync('yarn', ['tsc', '--project', 'tsconfig/publish-types.json']); + + // This is rooted in the `TYPES_DIR` so that the result is just the names of + // the modules, as generated directly from the tsconfig above. + let moduleNames = glob.sync('**/*.d.ts', { + ignore: 'index.d.ts', // ignore the root file itself if it somehow exists + cwd: TYPES_DIR, + }); + + for (let moduleName of moduleNames) { + wrapInDeclareModule(moduleName); + } + + let sideEffectModules = moduleNames + .filter((module) => !PREVIEW_MODULES.includes(module)) + .map((moduleName) => `import './${moduleName}';`) + .join('\n'); + + let stableIndexDTsContents = BASE_INDEX_D_TS.replace(MODULES_PLACEHOLDER, sideEffectModules); + fs.writeFileSync(path.join(TYPES_DIR, 'index.d.ts'), stableIndexDTsContents); +} + +/** + * @param {string} moduleName + */ +function wrapInDeclareModule(moduleName) { + let modulePath = path.join(TYPES_DIR, moduleName); + + /** @type {string} */ + let contents; + try { + contents = fs.readFileSync(modulePath, { encoding: 'utf-8' }); + } catch (e) { + console.error(`Error reading ${modulePath}: ${e}`); + return; + } + + let moduleNameForDeclaration = moduleName.replace('/index.d.ts', ''); + + let string = new MagicString(contents); + string.indent(' ').prepend(`declare module '${moduleNameForDeclaration}' {\n`).append('}\n'); + + try { + fs.writeFileSync(modulePath, string.toString()); + } catch (e) { + console.error(`Error writing ${modulePath}: ${e}`); + } +} + +// Run it! +main(); diff --git a/types/stable/index.d.ts b/types/stable/index.d.ts deleted file mode 100644 index b55ef1447a1..00000000000 --- a/types/stable/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Future home of stable Ember types! This file currently does nothing, but in -// the months ahead will look more and more like the `/types/preview/index.d.ts` -// while that one empties. This is just here so that someone who writes the -// import we recommend-- -// -// ```ts -// import 'ember-source/types'; -// import 'ember-source/types/preview'; -// ``` -// -// --will not get warnings from TypeScript while we wait on putting actual -// things here! diff --git a/yarn.lock b/yarn.lock index 9632a9b374f..809399201d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7711,6 +7711,13 @@ magic-string@^0.25.2, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" +magic-string@^0.26.7: + version "0.26.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" + integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow== + dependencies: + sourcemap-codec "^1.4.8" + make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -9980,6 +9987,11 @@ sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" integrity "sha1-xj6pJ8Ap3WvZorf6A7P+wCrVbp8= sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==" +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + sourcemap-validator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/sourcemap-validator/-/sourcemap-validator-1.1.0.tgz#00454547d1682186e1498a7208e022e8dfa8738f" From 752f13318b35613c7c0b617a89999e0ba66c1e41 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 21 Nov 2022 19:20:10 -0700 Subject: [PATCH 4/9] Exclude generated stable types from Git --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index f2f07319406..0f018875cdc 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,9 @@ npm-debug.log *.log /.vscode +# These are automatically generated by our build process. +# TODO: make that *fully* true: The root types/stable directory is *not* +# automatically generated yet, and accordingly we have explicitly committed a +# couple of the files. Once it is, we can switch this over to just ignoring +# `types/stable` entirely. +types/stable From 63e59ca6460e5378736ab3bf36240e5fdb5e4df5 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Mon, 21 Nov 2022 19:17:39 -0700 Subject: [PATCH 5/9] Update module-level documentation for preview types Align the text with the module docs for the stable types, clarifying the updated current status. --- types/preview/index.d.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/types/preview/index.d.ts b/types/preview/index.d.ts index b106926d952..0ffda874e62 100644 --- a/types/preview/index.d.ts +++ b/types/preview/index.d.ts @@ -1,11 +1,21 @@ /** - @module - Makes Ember's types for the packages it publishes available by importing this - path from `ember-source`: + Provides stable type definitions for Ember.js. It is maintained by hand, and + the types provided here are unstable and subject to change without warning, + though we make a best effort to keep churn to a minimum while we work to + stabilize Ember's types. + + To use these type definitions, add these import to any TypeScript file in your + Ember app or addon: ```ts - import 'ember-source/preview'; + import 'ember-source/types'; + import 'ember-source/types/preview'; ``` + + As Ember's types become more stable, this will automatically give you the + latest mix of stable and preview types, with no effort from you. + + @module */ // This works because each of these modules presents `declare module` definition From 24c0e4a8a30bcc624d4d47272eae1901ad346f90 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Tue, 22 Nov 2022 07:01:31 -0700 Subject: [PATCH 6/9] Tweak/improve/fix type side-effect package docs --- types/preview/index.d.ts | 9 +++++---- types/publish.mjs | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/types/preview/index.d.ts b/types/preview/index.d.ts index 0ffda874e62..30e11e44d55 100644 --- a/types/preview/index.d.ts +++ b/types/preview/index.d.ts @@ -1,8 +1,9 @@ /** - Provides stable type definitions for Ember.js. It is maintained by hand, and - the types provided here are unstable and subject to change without warning, - though we make a best effort to keep churn to a minimum while we work to - stabilize Ember's types. + *Provides **preview** type definitions for Ember.js.* + + These types are maintained by hand and the types provided here are unstable + and subject to change without warning, though we make a best effort to keep + churn to a minimum while we work to stabilize Ember's types. To use these type definitions, add these import to any TypeScript file in your Ember app or addon: diff --git a/types/publish.mjs b/types/publish.mjs index 7bfb209bd96..e369bebac4b 100755 --- a/types/publish.mjs +++ b/types/publish.mjs @@ -340,8 +340,10 @@ const MODULES_PLACEHOLDER = '~~~MODULES GO HERE~~~'; const BASE_INDEX_D_TS = `\ /** - Provides stable type definitions for Ember.js. It is generated automatically - as part of Ember's publishing process and should never be edited manually. + *Provides **stable** type definitions for Ember.js.* + + This module is generated automatically as part of Ember's publishing process and + should never be edited manually. To use these type definitions, add this import to any TypeScript file in your Ember app or addon: From f37f561ac5a333d577749cb80c37b17ecdfef5c0 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Tue, 22 Nov 2022 08:10:56 -0700 Subject: [PATCH 7/9] Fix incomplete sentence in stable types module doc --- types/publish.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/publish.mjs b/types/publish.mjs index e369bebac4b..93d696979e6 100755 --- a/types/publish.mjs +++ b/types/publish.mjs @@ -369,7 +369,7 @@ const BASE_INDEX_D_TS = `\ // same work, but automatically. // STATUS NOTE: this does not yet include Ember's full public API, only the -// subset of it for which we have determined +// subset of it for which we have determined the types are ready to stabilize. // // Over time, it will come to include *all* of Ember's types, and the matching // \`preview\` types will become empty. This is means that someone who writes the From 7bef3141fa8ed1c82f213e5d95a661186b9324cd Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Tue, 22 Nov 2022 08:57:39 -0700 Subject: [PATCH 8/9] Fix typo in internal comments for @ember/object Co-authored-by: Dan Freeman --- packages/@ember/object/-internals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@ember/object/-internals.ts b/packages/@ember/object/-internals.ts index 2cdc10e94fd..922c4824e4a 100644 --- a/packages/@ember/object/-internals.ts +++ b/packages/@ember/object/-internals.ts @@ -13,7 +13,7 @@ import EmberObject from '.'; // binding below for `FrameworkObject`, TS gets stuck because this creates // `FrameworkObject` with a class expression (rather than the usual class // declaration form). That in turn means TS needs to be able to fully name the -// type produced by the clsas expression, which includes the `OWNER` symbol from +// type produced by the class expression, which includes the `OWNER` symbol from // `@glimmer/owner`. // // By explicitly giving the declaration a type when assigning it the class From 26a0923a4fa589c70f5a3ce1069daa803c02a5d3 Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Tue, 22 Nov 2022 09:08:37 -0700 Subject: [PATCH 9/9] Exit with `1` if `types/publish` script fails --- types/publish.mjs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/types/publish.mjs b/types/publish.mjs index 93d696979e6..f4dce2f9d4a 100755 --- a/types/publish.mjs +++ b/types/publish.mjs @@ -401,8 +401,12 @@ async function main() { cwd: TYPES_DIR, }); + let status = 'success'; for (let moduleName of moduleNames) { - wrapInDeclareModule(moduleName); + let result = wrapInDeclareModule(moduleName); + if (result !== 'success') { + status = result; + } } let sideEffectModules = moduleNames @@ -412,10 +416,13 @@ async function main() { let stableIndexDTsContents = BASE_INDEX_D_TS.replace(MODULES_PLACEHOLDER, sideEffectModules); fs.writeFileSync(path.join(TYPES_DIR, 'index.d.ts'), stableIndexDTsContents); + + process.exit(status === 'success' ? 0 : 1); } /** * @param {string} moduleName + * @return {'success' | 'failure'} */ function wrapInDeclareModule(moduleName) { let modulePath = path.join(TYPES_DIR, moduleName); @@ -426,7 +433,7 @@ function wrapInDeclareModule(moduleName) { contents = fs.readFileSync(modulePath, { encoding: 'utf-8' }); } catch (e) { console.error(`Error reading ${modulePath}: ${e}`); - return; + return 'failure'; } let moduleNameForDeclaration = moduleName.replace('/index.d.ts', ''); @@ -438,7 +445,10 @@ function wrapInDeclareModule(moduleName) { fs.writeFileSync(modulePath, string.toString()); } catch (e) { console.error(`Error writing ${modulePath}: ${e}`); + return 'failure'; } + + return 'success'; } // Run it!