ember.nvp 0.7.0 → 1.1.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.
Files changed (47) hide show
  1. package/.github/workflows/ci.yml +6 -0
  2. package/.release-plan.json +7 -59
  3. package/CHANGELOG.md +46 -0
  4. package/package.json +1 -1
  5. package/packages/rolldown/README.md +68 -1
  6. package/packages/rolldown/index.ts +8 -4
  7. package/packages/rolldown/package.json +13 -4
  8. package/packages/rolldown/rolldown.config.js +5 -3
  9. package/packages/rolldown/src/app-reexports.test.ts +220 -0
  10. package/packages/rolldown/src/app-reexports.ts +152 -0
  11. package/packages/rolldown/src/config.ts +50 -0
  12. package/packages/rolldown/src/dts.test.ts +166 -0
  13. package/packages/rolldown/src/transform.test.ts +173 -0
  14. package/src/bases/minimal-extension/extension.test.ts +251 -0
  15. package/src/bases/minimal-extension/files/.env.development +8 -0
  16. package/src/bases/minimal-extension/files/README.md +36 -0
  17. package/src/bases/minimal-extension/files/app/app.ts +21 -0
  18. package/src/bases/minimal-extension/files/app/boot.ts +8 -0
  19. package/src/bases/minimal-extension/files/app/config.ts +22 -0
  20. package/src/bases/minimal-extension/files/app/router.ts +10 -0
  21. package/src/bases/minimal-extension/files/app/templates/application.gts +5 -0
  22. package/src/bases/minimal-extension/files/babel.config.js +49 -0
  23. package/src/bases/minimal-extension/files/index.html +17 -0
  24. package/src/bases/minimal-extension/files/package.json +58 -0
  25. package/src/bases/minimal-extension/files/public/manifest.json +9 -0
  26. package/src/bases/minimal-extension/files/tsconfig.json +13 -0
  27. package/src/bases/minimal-extension/files/vite.config.mjs +6 -0
  28. package/src/bases/minimal-extension/index.js +142 -0
  29. package/src/bases/minimal-library/files/tsdown.config.js +2 -1
  30. package/src/cli/generator.js +4 -0
  31. package/src/cli/questions/project-type.js +9 -2
  32. package/src/layers/renovate/index.js +4 -3
  33. package/src/layers/typescript/index.js +6 -0
  34. package/src/utils/types.ts +1 -1
  35. package/test/ember.nvp/base-library.test.ts +41 -30
  36. package/test/ember.nvp/layer-publish-checks.test.ts +7 -10
  37. package/test/ember.nvp/layer-qunit.test.ts +17 -30
  38. package/test/ember.nvp/layer-vitest.test.ts +4 -18
  39. package/test/ember.nvp/permutations/app-checks.test.ts +3 -0
  40. package/test/ember.nvp/permutations/extension-1.test.ts +3 -0
  41. package/test/ember.nvp/permutations/extension-2.test.ts +3 -0
  42. package/test/ember.nvp/permutations/extension-checks.test.ts +3 -0
  43. package/test/ember.nvp/permutations/library-checks.test.ts +3 -0
  44. package/test/ember.nvp/permutations/shared.ts +68 -19
  45. package/test/test-helpers.ts +25 -5
  46. package/vitest.config.js +1 -1
  47. package/packages/rolldown/src/define-config.ts +0 -32
@@ -0,0 +1,251 @@
1
+ import { beforeAll, describe, it, expect as hardExpect, afterAll } from "vitest";
2
+ import { generate, build, reapply } from "#test-helpers";
3
+ import { packageJson } from "ember-apply";
4
+
5
+ import type { Project } from "ember.nvp";
6
+ import { readFile, rm } from "node:fs/promises";
7
+ import { globSync } from "node:fs";
8
+ import { join } from "node:path";
9
+
10
+ const expect = hardExpect.soft;
11
+
12
+ describe("typescript", () => {
13
+ let project: Project;
14
+ beforeAll(async () => {
15
+ project = await generate({
16
+ name: "my-extension",
17
+ type: "extension",
18
+ layers: ["typescript"],
19
+ });
20
+
21
+ let { exitCode } = await project.install();
22
+
23
+ hardExpect(exitCode, "Install succeeds").toBe(0);
24
+ });
25
+
26
+ afterAll(async () => {
27
+ await rm(project.directory, { recursive: true, force: true });
28
+ });
29
+
30
+ it("project check", () => {
31
+ expect(project.desires.layers.map((layer) => layer.name)).toMatchInlineSnapshot(`
32
+ [
33
+ "typescript",
34
+ ]
35
+ `);
36
+ expect(project.wantsTypeScript).toBe(true);
37
+ expect(project.type).toBe("extension");
38
+ });
39
+
40
+ it("has the typescript babel plugin", async () => {
41
+ let content = await project.read("babel.config.js");
42
+
43
+ expect(content).toContain("@babel/plugin-transform-typescript");
44
+ });
45
+
46
+ it("named the extension in the manifest", async () => {
47
+ let manifest = JSON.parse((await project.read("public/manifest.json"))!);
48
+
49
+ expect(manifest.name).toBe("my-extension");
50
+ expect(manifest.manifest_version).toBe(3);
51
+ });
52
+
53
+ it("boots from an external script (extension CSP forbids inline scripts)", async () => {
54
+ let html = await project.read("index.html");
55
+
56
+ expect(html).toContain(`src="./app/boot.ts"`);
57
+ expect(html).not.toContain("Application.create");
58
+ });
59
+
60
+ it("build for development (testing, etc)", async () => {
61
+ let { exitCode } = await build(project);
62
+
63
+ expect(exitCode).toBe(0);
64
+ });
65
+
66
+ it("build for production", async () => {
67
+ let { exitCode } = await build(project, "production");
68
+
69
+ expect(exitCode).toBe(0);
70
+ });
71
+
72
+ it("dist is loadable as an unpacked extension", async () => {
73
+ let manifest = JSON.parse(
74
+ await readFile(join(project.directory, "dist/manifest.json"), "utf-8"),
75
+ );
76
+
77
+ expect(manifest.name).toBe("my-extension");
78
+ expect(manifest.action.default_popup).toBe("index.html");
79
+
80
+ let html = await readFile(join(project.directory, "dist/index.html"), "utf-8");
81
+
82
+ expect(html).toContain(`<script type="module"`);
83
+ expect(html).not.toContain("Application.create");
84
+ });
85
+
86
+ it("has no JS files", async () => {
87
+ let results = globSync("app/**/*", {
88
+ exclude: ["node_modules", "dist"],
89
+ cwd: project.directory,
90
+ });
91
+ expect(results).toMatchInlineSnapshot(`
92
+ [
93
+ "app/app.ts",
94
+ "app/boot.ts",
95
+ "app/config.ts",
96
+ "app/router.ts",
97
+ "app/templates",
98
+ "app/templates/application.gts",
99
+ ]
100
+ `);
101
+ });
102
+
103
+ it("when re-applying, it no-ops correctly", async () => {
104
+ let files = new Set(
105
+ globSync("**/*", { cwd: project.directory, exclude: ["node_modules", "dist"] }),
106
+ );
107
+
108
+ await reapply(project, []);
109
+
110
+ let filesAfter = new Set(
111
+ globSync("**/*", { cwd: project.directory, exclude: ["node_modules", "dist"] }),
112
+ );
113
+
114
+ expect(filesAfter).toEqual(files);
115
+ expect(files).toMatchInlineSnapshot(`
116
+ Set {
117
+ "README.md",
118
+ "app",
119
+ "babel.config.js",
120
+ "index.html",
121
+ "package.json",
122
+ "pnpm-lock.yaml",
123
+ "public",
124
+ "tsconfig.json",
125
+ "vite.config.mjs",
126
+ "public/manifest.json",
127
+ "app/app.ts",
128
+ "app/boot.ts",
129
+ "app/config.ts",
130
+ "app/router.ts",
131
+ "app/templates",
132
+ "app/templates/application.gts",
133
+ }
134
+ `);
135
+ });
136
+ });
137
+
138
+ describe("javascript", () => {
139
+ let project: Project;
140
+ beforeAll(async () => {
141
+ project = await generate({
142
+ name: "my-extension",
143
+ type: "extension",
144
+ layers: [],
145
+ });
146
+
147
+ let { exitCode } = await project.install();
148
+
149
+ hardExpect(exitCode, "Install succeeds").toBe(0);
150
+ });
151
+
152
+ afterAll(async () => {
153
+ await rm(project.directory, { recursive: true, force: true });
154
+ });
155
+
156
+ it("project check", () => {
157
+ expect(project.desires.layers.map((layer) => layer.name)).toMatchInlineSnapshot(`[]`);
158
+ expect(project.wantsTypeScript).toBe(false);
159
+ expect(project.type).toBe("extension");
160
+ });
161
+
162
+ it("does not have the typescript babel plugin", async () => {
163
+ let content = await project.read("babel.config.js");
164
+
165
+ expect(content).not.toContain("@babel/plugin-transform-typescript");
166
+ });
167
+
168
+ it("updated the sub-path imports to not have ts extensions", async () => {
169
+ let manifest = await packageJson.read(project.directory);
170
+
171
+ expect(manifest.imports).toMatchInlineSnapshot(`
172
+ {
173
+ "#app/*": "./app/*",
174
+ "#components/*": "./app/components/*",
175
+ "#config": "./app/config.js",
176
+ "#services/*": "./app/services/*",
177
+ "#test-helpers/*": "./tests/helpers/*",
178
+ "#utils/*": "./app/utils/*",
179
+ }
180
+ `);
181
+ });
182
+
183
+ it("the popup points at the renamed boot script", async () => {
184
+ let html = await project.read("index.html");
185
+
186
+ expect(html).toContain(`src="./app/boot.js"`);
187
+ expect(html).not.toContain("boot.ts");
188
+ });
189
+
190
+ it("build for development (testing, etc)", async () => {
191
+ let { exitCode } = await build(project);
192
+
193
+ expect(exitCode).toBe(0);
194
+ });
195
+
196
+ it("build for production", async () => {
197
+ let { exitCode } = await build(project, "production");
198
+
199
+ expect(exitCode).toBe(0);
200
+ });
201
+
202
+ it("has no TS files", async () => {
203
+ let results = globSync("app/**/*", {
204
+ exclude: ["node_modules", "dist"],
205
+ cwd: project.directory,
206
+ });
207
+ expect(results).toMatchInlineSnapshot(`
208
+ [
209
+ "app/app.js",
210
+ "app/boot.js",
211
+ "app/config.js",
212
+ "app/router.js",
213
+ "app/templates",
214
+ "app/templates/application.gjs",
215
+ ]
216
+ `);
217
+ });
218
+
219
+ it("when re-applying, it no-ops correctly", async () => {
220
+ let files = new Set(
221
+ globSync("**/*", { cwd: project.directory, exclude: ["node_modules", "dist"] }),
222
+ );
223
+
224
+ await reapply(project, []);
225
+
226
+ let filesAfter = new Set(
227
+ globSync("**/*", { cwd: project.directory, exclude: ["node_modules", "dist"] }),
228
+ );
229
+
230
+ expect(filesAfter).toEqual(files);
231
+ expect(files).toMatchInlineSnapshot(`
232
+ Set {
233
+ "README.md",
234
+ "app",
235
+ "babel.config.js",
236
+ "index.html",
237
+ "package.json",
238
+ "pnpm-lock.yaml",
239
+ "public",
240
+ "vite.config.mjs",
241
+ "public/manifest.json",
242
+ "app/app.js",
243
+ "app/boot.js",
244
+ "app/config.js",
245
+ "app/router.js",
246
+ "app/templates",
247
+ "app/templates/application.gjs",
248
+ }
249
+ `);
250
+ });
251
+ });
@@ -0,0 +1,8 @@
1
+ # This file is committed to git and should not contain any secrets.
2
+ #
3
+ # Vite recommends using .env.local or .env.[mode].local if you need to manage secrets
4
+ # SEE: https://vite.dev/guide/env-and-mode.html#env-files for more information.
5
+
6
+
7
+ # Default NODE_ENV with vite build --mode=test is production
8
+ NODE_ENV=development
@@ -0,0 +1,36 @@
1
+ # minimal-extension
2
+
3
+ A [Manifest V3](https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3) browser extension whose popup is an Ember app.
4
+
5
+ ## Developing
6
+
7
+ The popup is a regular web page, so the fastest feedback loop is the vite dev server in a normal browser tab:
8
+
9
+ ```bash
10
+ pnpm dev
11
+ ```
12
+
13
+ To run as an actual extension, build (in watch mode) and load the `dist/` directory as an unpacked extension:
14
+
15
+ ```bash
16
+ pnpm build:watch
17
+ ```
18
+
19
+ - **Chrome / Edge / Brave**: `chrome://extensions` → enable "Developer mode" → "Load unpacked" → select `dist/`
20
+ - **Firefox**: `about:debugging#/runtime/this-firefox` → "Load Temporary Add-on…" → select `dist/manifest.json`
21
+
22
+ After a rebuild, re-open the popup (or reload the extension) to see changes.
23
+
24
+ ## Anatomy
25
+
26
+ - `public/manifest.json` — the extension manifest; copied verbatim into `dist/`. Add [`background`](https://developer.chrome.com/docs/extensions/reference/manifest/background), [`content_scripts`](https://developer.chrome.com/docs/extensions/reference/manifest/content-scripts), permissions, and icons here as your extension grows.
27
+ - `index.html` — the popup page (`action.default_popup`). Extension pages are served with a `script-src 'self'` Content-Security-Policy, so the app boots from `app/boot.ts` rather than an inline script.
28
+ - `app/` — a normal Ember app. Routing uses `locationType: "none"`, since extension pages have no URL bar to bind to.
29
+
30
+ ## Building for release
31
+
32
+ ```bash
33
+ pnpm build
34
+ ```
35
+
36
+ Zip the contents of `dist/` and upload it to the extension store(s) of your choosing.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Looking for services that come from addons?
3
+ *
4
+ * See: https://github.com/embroider-build/embroider/issues/2659
5
+ *
6
+ * We currently don't support app-tree merging from libraries.
7
+ *
8
+ * For services, I highly recommend looking in to either of
9
+ * - https://github.com/chancancode/ember-polaris-service-
10
+ * - https://ember-primitives.pages.dev/6-utils/createService.md
11
+ * - https://ember-primitives.pages.dev/6-utils/createAsyncService.md
12
+ */
13
+ import Application from "ember-strict-application-resolver";
14
+
15
+ export default class App extends Application {
16
+ modules = {
17
+ ...import.meta.glob("./router.*", { eager: true }),
18
+ ...import.meta.glob("./templates/**/*", { eager: true }),
19
+ ...import.meta.glob("./services/**/*", { eager: true }),
20
+ };
21
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Extension pages disallow inline scripts (`script-src 'self'`),
3
+ * so booting the app lives here instead of in index.html.
4
+ */
5
+ import Application from "#app/app";
6
+ import environment from "#config";
7
+
8
+ Application.create(environment.APP);
@@ -0,0 +1,22 @@
1
+ interface Config {
2
+ environment: "development" | "production";
3
+ locationType: "history" | "hash" | "none" | "auto";
4
+ rootURL: string;
5
+ EmberENV?: Record<string, unknown>;
6
+ APP: Record<string, unknown> & { rootElement?: string; autoboot?: boolean };
7
+ }
8
+
9
+ const ENV: Config = {
10
+ environment: import.meta.env.DEV ? "development" : "production",
11
+ rootURL: "/",
12
+ /**
13
+ * Extension pages live at chrome-extension://<id>/index.html (or
14
+ * moz-extension://), where history-based routing has nothing to bind
15
+ * to. "none" keeps routing fully programmatic.
16
+ */
17
+ locationType: "none",
18
+ EmberENV: {},
19
+ APP: {},
20
+ };
21
+
22
+ export default ENV;
@@ -0,0 +1,10 @@
1
+ import EmbroiderRouter from "@embroider/router";
2
+
3
+ import config from "#config";
4
+
5
+ export default class Router extends EmbroiderRouter {
6
+ location = config.locationType;
7
+ rootURL = config.rootURL;
8
+ }
9
+
10
+ Router.map(function () {});
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <h1>Welcome to Ember</h1>
3
+
4
+ {{outlet}}
5
+ </template>
@@ -0,0 +1,49 @@
1
+ import { buildMacros } from "@embroider/macros/babel";
2
+
3
+ const macros = buildMacros({
4
+ configure(config) {
5
+ if (process.env.EMBER_ENV === "test") {
6
+ config.enableRuntimeMode();
7
+ }
8
+ },
9
+ });
10
+
11
+ export default {
12
+ plugins: [
13
+ [
14
+ "@babel/plugin-transform-typescript",
15
+ {
16
+ allExtensions: true,
17
+ onlyRemoveTypeImports: true,
18
+ allowDeclareFields: true,
19
+ },
20
+ ],
21
+ [
22
+ "babel-plugin-ember-template-compilation",
23
+ {
24
+ transforms: [...macros.templateMacros],
25
+ },
26
+ ],
27
+ [
28
+ "module:decorator-transforms",
29
+ {
30
+ runtime: {
31
+ import: import.meta.resolve("decorator-transforms/runtime-esm"),
32
+ },
33
+ },
34
+ ],
35
+ [
36
+ "@babel/plugin-transform-runtime",
37
+ {
38
+ absoluteRuntime: import.meta.dirname,
39
+ useESModules: true,
40
+ regenerator: false,
41
+ },
42
+ ],
43
+ ...macros.babelMacros,
44
+ ],
45
+
46
+ generatorOpts: {
47
+ compact: false,
48
+ },
49
+ };
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Minimal Extension</title>
6
+ <meta name="description" content="" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ </head>
9
+ <body>
10
+ <!--
11
+ Unlike a web app, this boot script cannot be inlined:
12
+ extension pages are served with a Content-Security-Policy of
13
+ `script-src 'self'`, which forbids inline scripts.
14
+ -->
15
+ <script type="module" src="./app/boot.ts"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "minimal-extension",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "Your Browser Extension's description here",
6
+ "repository": "",
7
+ "license": "MIT",
8
+ "author": "",
9
+ "type": "module",
10
+ "imports": {
11
+ "#app/*": "./app/*",
12
+ "#config": "./app/config.ts",
13
+ "#components/*": "./app/components/*",
14
+ "#services/*": "./app/services/*",
15
+ "#test-helpers/*": "./tests/helpers/*",
16
+ "#utils/*": "./app/utils/*"
17
+ },
18
+ "exports": {
19
+ "./tests/*": "./tests/*",
20
+ "./*": "./app/*"
21
+ },
22
+ "scripts": {
23
+ "build": "vite build",
24
+ "build:watch": "vite build --watch --mode development",
25
+ "dev": "vite",
26
+ "start": "vite"
27
+ },
28
+ "dependencies": {
29
+ "@embroider/macros": "^1.20.3",
30
+ "@embroider/router": "^3.0.6",
31
+ "@glimmer/component": "^2.1.1",
32
+ "decorator-transforms": "^2.3.2",
33
+ "ember-source": "~7.0.0",
34
+ "ember-strict-application-resolver": "^0.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "@babel/core": "^7.29.7",
38
+ "@babel/plugin-transform-runtime": "^7.29.7",
39
+ "@babel/plugin-transform-typescript": "^7.29.7",
40
+ "@babel/runtime": "^7.29.7",
41
+ "@ember/app-tsconfig": "^2.0.0",
42
+ "@embroider/core": "^4.4.2",
43
+ "@glint/ember-tsc": "^1.7.5",
44
+ "@glint/template": "^1.7.7",
45
+ "@glint/tsserver-plugin": "^2.5.5",
46
+ "@nullvoxpopuli/ember-vite": "workspace:*",
47
+ "@rollup/plugin-babel": "^7.1.0",
48
+ "babel-plugin-ember-template-compilation": "^4.0.0",
49
+ "typescript": "^6.0.3",
50
+ "vite": "^8.0.14"
51
+ },
52
+ "engines": {
53
+ "node": ">= 24"
54
+ },
55
+ "ember": {
56
+ "edition": "octane"
57
+ }
58
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "minimal-extension",
4
+ "version": "0.0.0",
5
+ "description": "Your Browser Extension's description here",
6
+ "action": {
7
+ "default_popup": "index.html"
8
+ }
9
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@ember/app-tsconfig",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "lib": ["esnext", "dom", "dom.iterable"],
6
+ "types": [
7
+ "ember-source/types",
8
+ "@embroider/core/virtual",
9
+ "vite/client",
10
+ "@glint/ember-tsc/types"
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import { ember } from "@nullvoxpopuli/ember-vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [ember()],
6
+ });
@@ -0,0 +1,142 @@
1
+ import { packageJson } from "ember-apply";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { getLatest } from "#utils/npm.js";
5
+ import { applyFolderTo } from "#utils/fs.js";
6
+ import { removeConfiguredPlugin } from "#utils/babel.js";
7
+
8
+ /**
9
+ * Minimal Extension Base
10
+ *
11
+ * Provides the absolute bare minimum for a browser extension (Manifest V3)
12
+ * whose popup is an Ember app:
13
+ * - type: "module" in package.json
14
+ * - public/manifest.json copied verbatim into dist
15
+ * - index.html popup booting from an external script (extension pages
16
+ * forbid inline scripts via CSP)
17
+ * - No testing framework
18
+ * - No linting or formatting
19
+ * - No ember-welcome-page
20
+ */
21
+ export default {
22
+ label: "Minimal Extension Base",
23
+ description: "Bare minimum browser extension structure with an Ember popup",
24
+
25
+ /**
26
+ * 1. Apply files
27
+ * a. Remove TS if needed
28
+ * 2. Remove TS deps/files if needed
29
+ * 3. Update name(s)
30
+ * 4. Upgrade in-range dependencies
31
+ * 5. Update an existing babel config, if any
32
+ *
33
+ * @param {import('#utils/project.js').Project} project
34
+ */
35
+ async run(project) {
36
+ await applyFiles(project);
37
+ await updateName(project);
38
+ await makeJavaScript(project);
39
+ await upgradeDependencies(project);
40
+ await updateBabelConfig(project);
41
+ },
42
+ };
43
+
44
+ /**
45
+ * @param {import('#utils/project.js').Project} project
46
+ */
47
+ async function updateBabelConfig(project) {
48
+ if (await project.hasOrWantsLayer("typescript")) return;
49
+
50
+ await removeConfiguredPlugin(project, "@babel/plugin-transform-typescript");
51
+ }
52
+
53
+ /**
54
+ * @param {import('#utils/project.js').Project} project
55
+ */
56
+ async function applyFiles(project) {
57
+ let filePath = join(import.meta.dirname, "files");
58
+
59
+ await applyFolderTo(filePath, project);
60
+ }
61
+
62
+ /**
63
+ * Operates on known files where the name matters
64
+ *
65
+ * @param {import('#utils/project.js').Project} project
66
+ */
67
+ async function updateName(project) {
68
+ await packageJson.modify((json) => {
69
+ json.name = project.desires.name;
70
+ }, project.directory);
71
+
72
+ if (!project.hasFile("public/manifest.json")) return;
73
+
74
+ let manifestPath = project.path("public/manifest.json");
75
+ let manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
76
+
77
+ manifest.name = project.desires.name;
78
+
79
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
80
+ }
81
+
82
+ /**
83
+ * @param {import('#utils/project.js').Project} project
84
+ */
85
+ async function makeJavaScript(project) {
86
+ if (await project.hasOrWantsLayer("typescript")) return;
87
+
88
+ /**
89
+ * We don't actually remove anything, because we want intellisense for JS
90
+ *
91
+ * But perhaps for the sake of minimal, we do remoev it.
92
+ * And we add a layer later for JSDoc ased TS or something
93
+ */
94
+ await packageJson.removeDevDependencies(
95
+ [
96
+ "@ember/app-tsconfig",
97
+ "@glint/ember-tsc",
98
+ "@glint/template",
99
+ "@glint/tsserver-plugin",
100
+ "typescript",
101
+ ],
102
+ project.directory,
103
+ );
104
+
105
+ await project.removeFile("tsconfig.json");
106
+
107
+ await packageJson.modify((json) => {
108
+ json.imports ||= {};
109
+ json.imports["#config"] = "./app/config.js";
110
+ }, project.directory);
111
+
112
+ /**
113
+ * The import-rewriting pass only handles module files; the popup's
114
+ * <script src> points at the boot module by path, so it has to follow
115
+ * the .ts -> .js rename by hand.
116
+ */
117
+ if (!project.hasFile("index.html")) return;
118
+
119
+ let indexPath = project.path("index.html");
120
+ let indexHtml = await readFile(indexPath, "utf-8");
121
+
122
+ await writeFile(indexPath, indexHtml.replace("./app/boot.ts", "./app/boot.js"));
123
+ }
124
+
125
+ /**
126
+ * Bumps in-range only.
127
+ * Majors will need to go through PR to this repo.
128
+ *
129
+ * @param {import('#utils/project.js').Project} project
130
+ */
131
+ async function upgradeDependencies(project) {
132
+ let existing = await packageJson.read(project.directory);
133
+
134
+ await packageJson.modify(async (json) => {
135
+ if (json.dependencies) {
136
+ Object.assign(json.dependencies, await getLatest(existing.dependencies));
137
+ }
138
+ if (json.devDependencies) {
139
+ Object.assign(json.devDependencies, await getLatest(existing.devDependencies));
140
+ }
141
+ }, project.directory);
142
+ }
@@ -1,4 +1,5 @@
1
- import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
1
+ import { defineConfig } from "tsdown";
2
+ import { ember } from "@nullvoxpopuli/ember-rolldown";
2
3
 
3
4
  export default defineConfig({
4
5
  entry: ["./src/index.ts"],