ember.nvp 1.0.0 → 1.2.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 (48) hide show
  1. package/.github/workflows/ci.yml +6 -0
  2. package/.release-plan.json +11 -15
  3. package/CHANGELOG.md +47 -0
  4. package/package.json +1 -1
  5. package/packages/rolldown/README.md +101 -1
  6. package/packages/rolldown/package.json +13 -4
  7. package/packages/rolldown/rolldown.config.js +5 -3
  8. package/packages/rolldown/src/app-reexports.test.ts +220 -0
  9. package/packages/rolldown/src/app-reexports.ts +152 -0
  10. package/packages/rolldown/src/babel.test.ts +17 -0
  11. package/packages/rolldown/src/babel.ts +21 -3
  12. package/packages/rolldown/src/config.ts +1 -1
  13. package/packages/rolldown/src/dts.test.ts +366 -0
  14. package/packages/rolldown/src/transform.test.ts +208 -0
  15. package/packages/rolldown/src/transform.ts +64 -31
  16. package/src/bases/minimal-extension/extension.test.ts +251 -0
  17. package/src/bases/minimal-extension/files/.env.development +8 -0
  18. package/src/bases/minimal-extension/files/README.md +36 -0
  19. package/src/bases/minimal-extension/files/app/app.ts +21 -0
  20. package/src/bases/minimal-extension/files/app/boot.ts +8 -0
  21. package/src/bases/minimal-extension/files/app/config.ts +22 -0
  22. package/src/bases/minimal-extension/files/app/router.ts +10 -0
  23. package/src/bases/minimal-extension/files/app/templates/application.gts +5 -0
  24. package/src/bases/minimal-extension/files/babel.config.js +49 -0
  25. package/src/bases/minimal-extension/files/index.html +17 -0
  26. package/src/bases/minimal-extension/files/package.json +58 -0
  27. package/src/bases/minimal-extension/files/public/manifest.json +9 -0
  28. package/src/bases/minimal-extension/files/tsconfig.json +13 -0
  29. package/src/bases/minimal-extension/files/vite.config.mjs +6 -0
  30. package/src/bases/minimal-extension/index.js +142 -0
  31. package/src/cli/generator.js +4 -0
  32. package/src/cli/questions/project-type.js +9 -2
  33. package/src/layers/renovate/index.js +4 -3
  34. package/src/layers/typescript/index.js +6 -0
  35. package/src/utils/types.ts +1 -1
  36. package/test/ember.nvp/base-library.test.ts +39 -32
  37. package/test/ember.nvp/layer-publish-checks.test.ts +4 -13
  38. package/test/ember.nvp/layer-qunit.test.ts +13 -30
  39. package/test/ember.nvp/layer-vitest.test.ts +4 -18
  40. package/test/ember.nvp/permutations/app-checks.test.ts +3 -0
  41. package/test/ember.nvp/permutations/extension-1.test.ts +3 -0
  42. package/test/ember.nvp/permutations/extension-2.test.ts +3 -0
  43. package/test/ember.nvp/permutations/extension-checks.test.ts +3 -0
  44. package/test/ember.nvp/permutations/library-checks.test.ts +3 -0
  45. package/test/ember.nvp/permutations/shared.ts +68 -19
  46. package/test/ember.nvp/scoped-css-library.test.ts +211 -0
  47. package/test/test-helpers.ts +25 -30
  48. package/vitest.config.js +1 -1
@@ -38,13 +38,32 @@ async function fixDtsExtensionsInDir(dir: string): Promise<void> {
38
38
  }
39
39
  }
40
40
 
41
+ /**
42
+ * A sourcemap mapping every generated line to the same line of the input.
43
+ *
44
+ * `AAAA` is the [0, 0, 0, 0] VLQ segment (first line maps to source 0, line 0,
45
+ * column 0); `AACA` is [0, 0, +1, 0] (each following line advances the source
46
+ * line by one). Exact at line granularity, which is all the specifier rewrite
47
+ * can disturb.
48
+ */
49
+ function lineIdentityMap(id: string, source: string) {
50
+ return {
51
+ version: 3,
52
+ sources: [id],
53
+ sourcesContent: [source],
54
+ names: [],
55
+ mappings: source
56
+ .split("\n")
57
+ .map((_, line) => (line === 0 ? "AAAA" : "AACA"))
58
+ .join(";"),
59
+ };
60
+ }
61
+
41
62
  /**
42
63
  * Maps `.gts` -> `.ts` and `.gjs` -> `.js` (so rolldown can identify them as
43
64
  * ts/js) and preprocesses `<template>` via content-tag.
44
65
  */
45
66
  export function emberTransform(): Plugin {
46
- const virtualFiles = new Set<string>();
47
-
48
67
  return {
49
68
  name: "ember:transform",
50
69
 
@@ -55,41 +74,39 @@ export function emberTransform(): Plugin {
55
74
 
56
75
  const fileName = path.join(path.dirname(importer), id);
57
76
 
58
- if (id.endsWith(".gts") && !importer.endsWith(".d.ts")) {
59
- virtualFiles.add(fileName);
77
+ if (id.endsWith(".gts")) {
60
78
  return {
61
79
  id: fileName.replace(/\.gts$/, ".ts"),
62
80
  meta: { fileName },
63
81
  };
64
82
  }
65
83
 
66
- if (id.endsWith(".gjs") && !importer.endsWith(".d.ts")) {
67
- virtualFiles.add(fileName);
84
+ if (id.endsWith(".gjs")) {
68
85
  return {
69
86
  id: fileName.replace(/\.gjs$/, ".js"),
70
87
  meta: { fileName },
71
88
  };
72
89
  }
73
90
 
74
- if (id.endsWith(".d.ts") && importer.endsWith(".d.ts")) {
75
- const gtsFile = fileName.replace(".d.ts", ".gts");
76
- if (virtualFiles.has(gtsFile)) {
77
- return fileName;
78
- }
79
- }
80
-
81
- if (id.endsWith(".ts") && !importer.endsWith(".d.ts")) {
91
+ // A `.ts`/`.js` specifier whose only backing file is a `.gts`/`.gjs`
92
+ // resolves to the virtual module. This must also serve `.d.ts`
93
+ // importers: rolldown-plugin-dts resolves a declaration module's
94
+ // imports through the plugin pipeline (`this.resolve`), and — when the
95
+ // resolution is a source file — loads it (registering its declaration)
96
+ // before mapping the import to the declaration id. Virtual modules
97
+ // exist nowhere else, so if we don't answer here the import either
98
+ // fails to resolve (no file on disk) or, worse, resolves to a
99
+ // declaration id that was never registered.
100
+ if (id.endsWith(".ts")) {
82
101
  const gtsFileName = fileName.replace(/\.ts$/, ".gts");
83
102
  if (existsSync(gtsFileName) && !existsSync(fileName)) {
84
- virtualFiles.add(gtsFileName);
85
103
  return { id: fileName, meta: { fileName: gtsFileName } };
86
104
  }
87
105
  }
88
106
 
89
- if (id.endsWith(".js") && !importer.endsWith(".d.ts")) {
107
+ if (id.endsWith(".js")) {
90
108
  const gjsFileName = fileName.replace(/\.js$/, ".gjs");
91
109
  if (existsSync(gjsFileName) && !existsSync(fileName)) {
92
- virtualFiles.add(gjsFileName);
93
110
  return { id: fileName, meta: { fileName: gjsFileName } };
94
111
  }
95
112
  }
@@ -105,7 +122,18 @@ export function emberTransform(): Plugin {
105
122
  },
106
123
  async handler(id) {
107
124
  const meta = this.getModuleInfo(id)?.meta ?? {};
108
- const fileName = meta?.fileName;
125
+ let fileName = meta?.fileName;
126
+
127
+ // A virtual id can be loaded without having passed through our
128
+ // resolveId (rolldown-plugin-dts calls `this.load({ id })` with just
129
+ // the id), so no meta is attached: recover the backing file from disk.
130
+ if (!fileName && !existsSync(id)) {
131
+ if (id.endsWith(".ts") && existsSync(id.replace(/\.ts$/, ".gts"))) {
132
+ fileName = id.replace(/\.ts$/, ".gts");
133
+ } else if (id.endsWith(".js") && existsSync(id.replace(/\.js$/, ".gjs"))) {
134
+ fileName = id.replace(/\.js$/, ".gjs");
135
+ }
136
+ }
109
137
 
110
138
  if (fileName) {
111
139
  this.addWatchFile(fileName);
@@ -132,21 +160,26 @@ export function emberTransform(): Plugin {
132
160
  id: /\.(js|ts)$/,
133
161
  },
134
162
  handler(input, id) {
135
- if (input.includes(".gts") && id.endsWith(".d.ts")) {
136
- return input.replace(
137
- /(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
138
- (_m, q, p) => `${q}${p.replace(/\.gts$/, ".d.ts")}${q}`,
139
- );
140
- }
141
-
142
- if (input.includes(".gts") && !id.endsWith(".d.ts")) {
143
- return input.replace(
144
- /(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
145
- (_m, q, p) => `${q}${p.replace(/\.gts$/, ".ts")}${q}`,
146
- );
163
+ // Rewrite `.gts` specifiers to `.ts` — the (virtual) source id — in
164
+ // runtime modules AND declaration modules alike. Declaration modules
165
+ // must NOT be rewritten to `.d.ts`: rolldown-plugin-dts's resolver
166
+ // treats a source-file resolution as "load it (registering its
167
+ // declaration), then map to the declaration id", while a `.d.ts` id is
168
+ // returned as-is — unloadable when the module isn't registered yet
169
+ // (e.g. the `.gts` is only ever type-imported).
170
+ const output = input.replace(
171
+ /(['"`])((?:\.\.?\/|\/|@|[A-Za-z0-9_\-])[^'"]*?\.gts)\1/g,
172
+ (_m, q, p) => `${q}${p.replace(/\.gts$/, ".ts")}${q}`,
173
+ );
174
+
175
+ if (output === input) {
176
+ return null;
147
177
  }
148
178
 
149
- return input;
179
+ // The rewrite only shortens import specifiers in place, so a
180
+ // line-identity map is accurate to the line (and to the column for
181
+ // everything before the first rewritten specifier on a line).
182
+ return { code: output, map: lineIdentityMap(id, input) };
150
183
  },
151
184
  },
152
185
 
@@ -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
+ });