ember.nvp 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +17 -2
- package/.release-plan.json +88 -4
- package/CHANGELOG.md +57 -0
- package/package.json +3 -2
- package/packages/build-tooling-utils/README.md +37 -0
- package/packages/build-tooling-utils/index.ts +2 -0
- package/packages/build-tooling-utils/package.json +42 -0
- package/packages/build-tooling-utils/rolldown.config.js +18 -0
- package/packages/build-tooling-utils/src/extensions.ts +18 -0
- package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
- package/packages/rolldown/README.md +115 -0
- package/packages/rolldown/index.ts +56 -0
- package/packages/rolldown/package.json +59 -0
- package/packages/rolldown/rolldown.config.js +21 -0
- package/packages/rolldown/src/babel.ts +136 -0
- package/packages/rolldown/src/define-config.ts +32 -0
- package/packages/rolldown/src/externals.ts +83 -0
- package/packages/rolldown/src/isolated-declarations.ts +65 -0
- package/packages/rolldown/src/transform.ts +162 -0
- package/packages/vite/README.md +7 -21
- package/packages/vite/index.ts +1 -1
- package/packages/vite/package.json +4 -6
- package/packages/vite/src/ember.ts +129 -15
- package/packages/vite/src/test-html.ts +86 -0
- package/src/bases/minimal-app/files/app/router.ts +1 -0
- package/src/bases/minimal-app/files/tsconfig.json +1 -0
- package/src/bases/minimal-app/index.js +2 -2
- package/src/bases/minimal-library/files/README.md +41 -0
- package/src/bases/minimal-library/files/package.json +49 -0
- package/src/bases/minimal-library/files/tsconfig.json +10 -0
- package/src/bases/minimal-library/files/tsdown.config.js +6 -0
- package/src/bases/minimal-library/index.js +158 -2
- package/src/cli/index.js +21 -2
- package/src/cli/questions/layers.js +5 -2
- package/src/cli/questions/project-type.js +0 -5
- package/src/layers/are-the-types-wrong/index.js +74 -0
- package/src/layers/git/files/gitignore +14 -1
- package/src/layers/git/index.js +6 -5
- package/src/layers/github-actions/index.js +12 -8
- package/src/layers/index.js +1 -1
- package/src/layers/inspector-support/index.js +101 -0
- package/src/layers/prettier/files/.prettierignore +4 -0
- package/src/layers/publint/index.js +68 -0
- package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
- package/src/layers/qunit/index.js +107 -7
- package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
- package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
- package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
- package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
- package/src/layers/qunit/library-files/vite.config.mjs +6 -0
- package/src/layers/typescript/index.js +43 -7
- package/src/layers/vitest/files/vitest.config.mjs +11 -11
- package/src/layers/vitest/index.js +70 -32
- package/src/utils/babel.js +9 -6
- package/src/utils/fs.js +44 -3
- package/src/utils/inspector-support.js +196 -0
- package/src/utils/npm.js +21 -7
- package/src/utils/remove-types.js +5 -0
- package/src/utils/rewrite-imports.js +157 -0
- package/src/utils/tsdown-config.js +85 -0
- package/src/utils/types.ts +5 -1
- package/test/ember.nvp/base-library.test.ts +232 -0
- package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
- package/test/ember.nvp/inspector-support.test.ts +179 -0
- package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
- package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
- package/test/ember.nvp/layer-qunit.test.ts +376 -15
- package/test/ember.nvp/layer-vitest.test.ts +250 -0
- package/test/ember.nvp/layers.test.ts +16 -0
- package/test/ember.nvp/library-src-fixtures.ts +81 -0
- package/test/ember.nvp/permutations/app-1.test.ts +3 -0
- package/test/ember.nvp/permutations/app-2.test.ts +3 -0
- package/test/ember.nvp/permutations/library-1.test.ts +3 -0
- package/test/ember.nvp/permutations/library-2.test.ts +3 -0
- package/test/ember.nvp/permutations/shared.ts +239 -0
- package/test/ember.nvp/rewrite-imports.test.ts +235 -0
- package/test/test-helpers/permutate.test.ts +17 -0
- package/test/test-helpers.ts +20 -7
- package/tsconfig.json +1 -0
- package/packages/vite/src/maybe-babel.ts +0 -219
- package/src/layers/qunit/files/run-tests.mjs +0 -30
- package/src/layers/vitest/files/tests/test-helper.ts +0 -5
- package/test/ember.nvp/permutations.test.ts +0 -200
- /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
- /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nullvoxpopuli/ember-vite",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Meta plugin for ember projects focusing on speed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -27,18 +27,16 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@babel/core": "^7.28.10",
|
|
29
29
|
"@embroider/core": "^4.4.2",
|
|
30
|
+
"@embroider/macros": "^1.20.3",
|
|
30
31
|
"@embroider/vite": "^1.5.0",
|
|
31
|
-
"@
|
|
32
|
-
"oxc-parser": "^0.132.0",
|
|
33
|
-
"oxc-transform": "^0.132.0",
|
|
34
|
-
"zimmerframe": "^1.1.4"
|
|
32
|
+
"@nullvoxpopuli/ember-build-tooling-utils": "workspace:*"
|
|
35
33
|
},
|
|
36
34
|
"engines": {
|
|
37
35
|
"node": ">= 24"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
40
38
|
"rolldown": "^1.1.2",
|
|
41
|
-
"typescript": "^
|
|
39
|
+
"typescript": "^6.0.3",
|
|
42
40
|
"vite": "^8.0.14"
|
|
43
41
|
}
|
|
44
42
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { buildMacros } from "@embroider/macros/babel";
|
|
1
2
|
import { ember as embroiderEmber, extensions, resolver, templateTag } from "@embroider/vite";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
4
5
|
import type { ConfigEnv, ResolvedConfig } from "vite";
|
|
5
6
|
|
|
6
|
-
import { maybeBabel } from "
|
|
7
|
+
import { maybeBabel } from "@nullvoxpopuli/ember-build-tooling-utils";
|
|
8
|
+
|
|
9
|
+
import { testHtml } from "./test-html.ts";
|
|
7
10
|
|
|
8
11
|
const emberConfig = embroiderEmber()[3]!;
|
|
9
12
|
|
|
@@ -12,6 +15,92 @@ function absolutePath(relativePath: string) {
|
|
|
12
15
|
return join(cwd, relativePath);
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Everything a project without its own babel config needs:
|
|
20
|
+
*
|
|
21
|
+
* - TypeScript stripping
|
|
22
|
+
* - template compilation (wire format: vite serves/bundles for the
|
|
23
|
+
* project's own ember-source, so the final compile happens here)
|
|
24
|
+
* - build macros, evaluated at build time
|
|
25
|
+
* - decorator-transforms, with its runtime left as a bare specifier so it
|
|
26
|
+
* resolves from the project (which depends on `decorator-transforms`)
|
|
27
|
+
*
|
|
28
|
+
* The plugins are named, not imported: babel resolves them from the
|
|
29
|
+
* project, which carries them as its own dependencies.
|
|
30
|
+
*/
|
|
31
|
+
function defaultBabelPlugins() {
|
|
32
|
+
const macros = buildMacros();
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
[
|
|
36
|
+
"@babel/plugin-transform-typescript",
|
|
37
|
+
{
|
|
38
|
+
allExtensions: true,
|
|
39
|
+
onlyRemoveTypeImports: true,
|
|
40
|
+
allowDeclareFields: true,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
["babel-plugin-ember-template-compilation", { transforms: [...macros.templateMacros] }],
|
|
44
|
+
["module:decorator-transforms", { runtime: { import: "decorator-transforms/runtime-esm" } }],
|
|
45
|
+
...macros.babelMacros,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type BabelOptions =
|
|
50
|
+
| { configFile: string }
|
|
51
|
+
| {
|
|
52
|
+
configFile: false;
|
|
53
|
+
babelrc: false;
|
|
54
|
+
babelHelpers: "inline";
|
|
55
|
+
plugins: ReturnType<typeof defaultBabelPlugins>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The project's babel config wins when it exists -- `babel.configFile`
|
|
60
|
+
* selects which one (e.g. a test-only `config/test/babel.config.js`).
|
|
61
|
+
* Without one (libraries), `defaultBabelPlugins` covers TypeScript,
|
|
62
|
+
* templates, macros, and decorators, so no config file is required --
|
|
63
|
+
* helpers are inlined because projects without a babel config don't
|
|
64
|
+
* carry `@babel/runtime`.
|
|
65
|
+
*/
|
|
66
|
+
function babelOptions(nvpConfig: Config): BabelOptions {
|
|
67
|
+
const configFile = resolve(
|
|
68
|
+
join(process.cwd(), nvpConfig.babel?.configFile ?? "./babel.config.js"),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (existsSync(configFile)) {
|
|
72
|
+
return { configFile };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
configFile: false,
|
|
77
|
+
babelrc: false,
|
|
78
|
+
babelHelpers: "inline",
|
|
79
|
+
plugins: defaultBabelPlugins(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Additional opt-ins beyond maybeBabel's built-in list.
|
|
85
|
+
*
|
|
86
|
+
* These libraries ship code that still needs babel (ember-concurrency's
|
|
87
|
+
* decorators, ember-scoped-css transforms, ember-intl's formatMessage macro),
|
|
88
|
+
* and the `initializeRuntimeMacrosConfig` runtime call must be compiled away.
|
|
89
|
+
*/
|
|
90
|
+
function babelFilter(nvpConfig: Config) {
|
|
91
|
+
return {
|
|
92
|
+
include: {
|
|
93
|
+
imports: [
|
|
94
|
+
"ember-concurrency",
|
|
95
|
+
"ember-scoped-css",
|
|
96
|
+
"ember-intl/helpers/format-message",
|
|
97
|
+
...(nvpConfig.babel?.include?.whenImporting ?? []),
|
|
98
|
+
],
|
|
99
|
+
code: [/\bintl\.formatMessage\b/, "initializeRuntimeMacrosConfig"] as (string | RegExp)[],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
15
104
|
interface Config {
|
|
16
105
|
babel?: {
|
|
17
106
|
/**
|
|
@@ -20,6 +109,16 @@ interface Config {
|
|
|
20
109
|
*/
|
|
21
110
|
parallel?: false;
|
|
22
111
|
|
|
112
|
+
/**
|
|
113
|
+
* which babel config file to use for this build, relative to the
|
|
114
|
+
* project root. defaults to "./babel.config.js".
|
|
115
|
+
*
|
|
116
|
+
* useful when the root babel config belongs to another pipeline
|
|
117
|
+
* (e.g. a library's publish build) and the vite build needs its own,
|
|
118
|
+
* such as "./config/test/babel.config.js".
|
|
119
|
+
*/
|
|
120
|
+
configFile?: string;
|
|
121
|
+
|
|
23
122
|
/**
|
|
24
123
|
* optional way to configure when babel is activateed.
|
|
25
124
|
* by default, all transforming is oxc, except when babel is needed
|
|
@@ -51,8 +150,8 @@ interface Config {
|
|
|
51
150
|
}
|
|
52
151
|
|
|
53
152
|
export function ember(nvpConfig: Config = {}) {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
153
|
+
const babel = babelOptions(nvpConfig);
|
|
154
|
+
const filter = babelFilter(nvpConfig);
|
|
56
155
|
|
|
57
156
|
/*
|
|
58
157
|
* Plugins must be returned as top-level array entries — NOT pushed into
|
|
@@ -98,16 +197,21 @@ export function ember(nvpConfig: Config = {}) {
|
|
|
98
197
|
// Libraries will have precompileTemplate and macros, etc,
|
|
99
198
|
// and we need to compile that away using this app's
|
|
100
199
|
// template compiler
|
|
101
|
-
maybeBabel({
|
|
200
|
+
maybeBabel({
|
|
201
|
+
...babel,
|
|
202
|
+
parallel: nvpConfig.babel?.parallel,
|
|
203
|
+
filter,
|
|
204
|
+
}),
|
|
102
205
|
];
|
|
103
206
|
},
|
|
104
207
|
},
|
|
105
208
|
resolver({ rolldown: true }),
|
|
106
209
|
templateTag(),
|
|
210
|
+
testHtml(),
|
|
107
211
|
maybeBabel({
|
|
108
|
-
|
|
212
|
+
...babel,
|
|
109
213
|
parallel: nvpConfig.babel?.parallel,
|
|
110
|
-
|
|
214
|
+
filter,
|
|
111
215
|
}),
|
|
112
216
|
];
|
|
113
217
|
}
|
|
@@ -146,13 +250,10 @@ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
146
250
|
viteConfig.build.rolldownOptions.input ||= {};
|
|
147
251
|
viteConfig.build.rolldownOptions.output ||= {};
|
|
148
252
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (existsSync("./tests.html")) {
|
|
253
|
+
// libraries don't, by default, have a "demo app" entrypoint
|
|
254
|
+
if (existsSync("./index.html")) {
|
|
154
255
|
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
155
|
-
|
|
256
|
+
main: absolutePath("./index.html"),
|
|
156
257
|
});
|
|
157
258
|
}
|
|
158
259
|
|
|
@@ -160,6 +261,16 @@ function dev(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
160
261
|
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
161
262
|
tests: absolutePath("./tests/index.html"),
|
|
162
263
|
});
|
|
264
|
+
} else if (existsSync("./tests.html")) {
|
|
265
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
266
|
+
tests: absolutePath("./tests.html"),
|
|
267
|
+
});
|
|
268
|
+
} else if (existsSync("./tests")) {
|
|
269
|
+
// no html of its own: the default-test-html plugin answers for
|
|
270
|
+
// tests/index.html
|
|
271
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
272
|
+
tests: absolutePath("./tests/index.html"),
|
|
273
|
+
});
|
|
163
274
|
}
|
|
164
275
|
|
|
165
276
|
Object.assign(viteConfig.build.rolldownOptions.output, {
|
|
@@ -203,9 +314,12 @@ function prod(viteConfig: ResolvedConfig, nvpConfig: Config) {
|
|
|
203
314
|
viteConfig.build.rolldownOptions.optimization ||= {};
|
|
204
315
|
viteConfig.build.rolldownOptions.output ||= {};
|
|
205
316
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
317
|
+
// libraries don't, by default, have a "demo app" entrypoint
|
|
318
|
+
if (existsSync("./index.html")) {
|
|
319
|
+
Object.assign(viteConfig.build.rolldownOptions.input, {
|
|
320
|
+
main: absolutePath("./index.html"),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
209
323
|
|
|
210
324
|
Object.assign(viteConfig.build.rolldownOptions.optimization, {
|
|
211
325
|
inlineConst: {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ViteDevServer } from "vite";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Everything a QUnit + testem run needs, with no project-specific
|
|
7
|
+
* references: root-relative specifiers keep it independent of where the
|
|
8
|
+
* project keeps its files, and the globs pick up tests/** as well as
|
|
9
|
+
* co-located `*-test` modules under src/**.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_TEST_HTML = `<!doctype html>
|
|
12
|
+
<html>
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="utf-8" />
|
|
15
|
+
<title>Tests</title>
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="qunit"></div>
|
|
20
|
+
<div id="qunit-fixture">
|
|
21
|
+
<div id="ember-testing-container">
|
|
22
|
+
<div id="ember-testing"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<script src="/testem.js" integrity="" data-embroider-ignore></script>
|
|
27
|
+
<script type="module">
|
|
28
|
+
import "ember-testing";
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { start } from "/tests/test-helper";
|
|
33
|
+
import.meta.glob("/tests/**/*.{js,ts,gjs,gts}", { eager: true });
|
|
34
|
+
import.meta.glob("/src/**/*-test.{js,ts,gjs,gts}", { eager: true });
|
|
35
|
+
start();
|
|
36
|
+
</script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lets a project run its tests without maintaining a tests/index.html:
|
|
43
|
+
* whenever that file is absent, this plugin answers for it -- `load`
|
|
44
|
+
* supplies the html to the build (so `tests/index.html` still lands in
|
|
45
|
+
* dist for testem), and the dev-server middleware serves it (through
|
|
46
|
+
* `transformIndexHtml`, so the html goes through the same pipeline as an
|
|
47
|
+
* on-disk file).
|
|
48
|
+
*
|
|
49
|
+
* A tests/index.html on disk always wins; every hook here defers to it.
|
|
50
|
+
*/
|
|
51
|
+
export function testHtml() {
|
|
52
|
+
const htmlPath = join(process.cwd(), "tests", "index.html");
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: "ember:default-test-html",
|
|
56
|
+
|
|
57
|
+
resolveId(id: string) {
|
|
58
|
+
if (id === htmlPath && !existsSync(htmlPath)) {
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
load(id: string) {
|
|
64
|
+
if (id === htmlPath && !existsSync(htmlPath)) {
|
|
65
|
+
return DEFAULT_TEST_HTML;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
configureServer(server: ViteDevServer) {
|
|
70
|
+
server.middlewares.use(async (req, res, next) => {
|
|
71
|
+
if (existsSync(htmlPath)) return next();
|
|
72
|
+
|
|
73
|
+
const url = (req.url ?? "").split("?")[0];
|
|
74
|
+
|
|
75
|
+
if (url !== "/tests" && url !== "/tests/" && url !== "/tests/index.html") {
|
|
76
|
+
return next();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const html = await server.transformIndexHtml("/tests/index.html", DEFAULT_TEST_HTML);
|
|
80
|
+
|
|
81
|
+
res.setHeader("Content-Type", "text/html");
|
|
82
|
+
res.end(html);
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { packageJson, js } from "ember-apply";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { getLatest } from "#utils/npm.js";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { applyFolderTo } from "#utils/fs.js";
|
|
5
5
|
import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
6
6
|
|
|
@@ -49,7 +49,7 @@ async function updateBabelConfig(project) {
|
|
|
49
49
|
* @param {import('#utils/project.js').Project} project
|
|
50
50
|
*/
|
|
51
51
|
async function applyFiles(project) {
|
|
52
|
-
let filePath =
|
|
52
|
+
let filePath = join(import.meta.dirname, "files");
|
|
53
53
|
|
|
54
54
|
await applyFolderTo(filePath, project);
|
|
55
55
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# minimal-library
|
|
2
|
+
|
|
3
|
+
An Ember v2 library (addon) built with
|
|
4
|
+
[`@nullvoxpopuli/ember-rolldown`](https://github.com/NullVoxPopuli/ember.nvp/tree/main/packages/rolldown)
|
|
5
|
+
and [tsdown](https://tsdown.dev/).
|
|
6
|
+
|
|
7
|
+
## Development
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Build the distributable (JS + `.d.ts` into `dist/`):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Rebuild on change while developing:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Structure
|
|
26
|
+
|
|
27
|
+
- `src/` — your library source. Author components in `.gts`/`.gjs`
|
|
28
|
+
(template-tag) and plain modules in `.ts`/`.js`.
|
|
29
|
+
- `src/index.ts` — the public entry point. Everything a consumer can import
|
|
30
|
+
must be re-exported from here (or added as an entry in `tsdown.config.js`).
|
|
31
|
+
- `dist/` — the built output that gets published (git-ignored).
|
|
32
|
+
|
|
33
|
+
Declarations are emitted with
|
|
34
|
+
[isolated declarations](https://www.typescriptlang.org/tsconfig/#isolatedDeclarations),
|
|
35
|
+
so every exported value needs an explicit type annotation — in particular,
|
|
36
|
+
annotate exported template-only components with `TOC<...>`.
|
|
37
|
+
|
|
38
|
+
## Publishing
|
|
39
|
+
|
|
40
|
+
`dist/` is built automatically on `prepack`, so `npm publish` ships the
|
|
41
|
+
compiled output plus your `src/`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "minimal-library",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Your Ember library's description here",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./*": {
|
|
15
|
+
"types": "./dist/*.d.ts",
|
|
16
|
+
"default": "./dist/*.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"prepack": "tsdown",
|
|
26
|
+
"start": "tsdown --watch"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"decorator-transforms": "^2.3.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@glimmer/component": "^2.0.0",
|
|
33
|
+
"ember-source": ">= 6.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@ember/library-tsconfig": "^2.0.0",
|
|
37
|
+
"@glimmer/component": "^2.1.1",
|
|
38
|
+
"@nullvoxpopuli/ember-rolldown": "workspace:*",
|
|
39
|
+
"ember-source": "~7.0.0",
|
|
40
|
+
"tsdown": "^0.22.7",
|
|
41
|
+
"typescript": "^6.0.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">= 24"
|
|
45
|
+
},
|
|
46
|
+
"ember": {
|
|
47
|
+
"edition": "octane"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,10 +1,166 @@
|
|
|
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 Library Base
|
|
10
|
+
*
|
|
11
|
+
* Provides the absolute bare minimum for an Ember v2 library (addon):
|
|
12
|
+
* - type: "module" in package.json
|
|
13
|
+
* - built with tsdown + @nullvoxpopuli/ember-rolldown
|
|
14
|
+
* - a sample `<template>` component and a plain module
|
|
15
|
+
* - No babel.config.js: ember() covers TS stripping, template compilation,
|
|
16
|
+
* and decorators
|
|
17
|
+
* - No testing framework
|
|
18
|
+
* - No linting or formatting
|
|
19
|
+
*
|
|
20
|
+
* Testing, linting, formatting, etc. are opt-in via layers.
|
|
21
|
+
*/
|
|
1
22
|
export default {
|
|
2
23
|
label: "Minimal Library Base",
|
|
3
|
-
description: "Bare minimum library structure",
|
|
24
|
+
description: "Bare minimum Ember v2 library (addon) structure",
|
|
4
25
|
|
|
5
26
|
/**
|
|
27
|
+
* 1. Apply files
|
|
28
|
+
* a. Remove TS if needed
|
|
29
|
+
* 2. Update name(s)
|
|
30
|
+
* 3. Make publishable
|
|
31
|
+
* 4. Remove TS deps/files/config if needed
|
|
32
|
+
* 5. Upgrade in-range dependencies
|
|
33
|
+
* 6. Update an existing babel config, if any
|
|
6
34
|
*
|
|
7
35
|
* @param {import('#utils/project.js').Project} project
|
|
8
36
|
*/
|
|
9
|
-
async run(project) {
|
|
37
|
+
async run(project) {
|
|
38
|
+
await applyFiles(project);
|
|
39
|
+
await updateName(project);
|
|
40
|
+
await makePublishable(project);
|
|
41
|
+
await makeJavaScript(project);
|
|
42
|
+
await upgradeDependencies(project);
|
|
43
|
+
await updateBabelConfig(project);
|
|
44
|
+
},
|
|
10
45
|
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('#utils/project.js').Project} project
|
|
49
|
+
*/
|
|
50
|
+
async function applyFiles(project) {
|
|
51
|
+
let filePath = join(import.meta.dirname, "files");
|
|
52
|
+
|
|
53
|
+
await applyFolderTo(filePath, project);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Operates on known files where the name matters
|
|
58
|
+
*
|
|
59
|
+
* @param {import('#utils/project.js').Project} project
|
|
60
|
+
*/
|
|
61
|
+
async function updateName(project) {
|
|
62
|
+
await packageJson.modify((json) => {
|
|
63
|
+
json.name = project.desires.name;
|
|
64
|
+
}, project.directory);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The template's package.json is `private: true` so it can never be
|
|
69
|
+
* published from this repo; the generated library exists to be published,
|
|
70
|
+
* so the flag is removed entirely.
|
|
71
|
+
*
|
|
72
|
+
* @param {import('#utils/project.js').Project} project
|
|
73
|
+
*/
|
|
74
|
+
async function makePublishable(project) {
|
|
75
|
+
await packageJson.modify((json) => {
|
|
76
|
+
delete json.private;
|
|
77
|
+
}, project.directory);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* When the project is JavaScript (no typescript layer), remove the TS
|
|
82
|
+
* toolchain, drop the tsconfig, and point the build at the emitted `.js`
|
|
83
|
+
* entry (declarations can't be produced without types).
|
|
84
|
+
*
|
|
85
|
+
* @param {import('#utils/project.js').Project} project
|
|
86
|
+
*/
|
|
87
|
+
async function makeJavaScript(project) {
|
|
88
|
+
if (await project.hasOrWantsLayer("typescript")) return;
|
|
89
|
+
|
|
90
|
+
await packageJson.removeDevDependencies(
|
|
91
|
+
["@babel/plugin-transform-typescript", "@ember/library-tsconfig", "typescript"],
|
|
92
|
+
project.directory,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await project.removeFile("tsconfig.json");
|
|
96
|
+
|
|
97
|
+
await removeTypesExports(project);
|
|
98
|
+
await pointBuildAtJavaScript(project);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* No declarations are emitted for a JavaScript library, so the `types`
|
|
103
|
+
* export conditions would point at files that never exist.
|
|
104
|
+
*
|
|
105
|
+
* @param {import('#utils/project.js').Project} project
|
|
106
|
+
*/
|
|
107
|
+
async function removeTypesExports(project) {
|
|
108
|
+
await packageJson.modify((json) => {
|
|
109
|
+
for (let condition of Object.values(json.exports ?? {})) {
|
|
110
|
+
if (condition && typeof condition === "object") {
|
|
111
|
+
delete condition.types;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, project.directory);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Rewrites the tsdown config so it builds the JavaScript entry and stops
|
|
119
|
+
* emitting declarations (defineConfig defaults to `dts: true`).
|
|
120
|
+
*
|
|
121
|
+
* @param {import('#utils/project.js').Project} project
|
|
122
|
+
*/
|
|
123
|
+
async function pointBuildAtJavaScript(project) {
|
|
124
|
+
let configPath = project.path("tsdown.config.js");
|
|
125
|
+
let contents = await readFile(configPath, "utf-8");
|
|
126
|
+
|
|
127
|
+
contents = contents.replace(
|
|
128
|
+
`entry: ["./src/index.ts"],`,
|
|
129
|
+
`entry: ["./src/index.js"],\n dts: false,`,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await writeFile(configPath, contents);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Bumps in-range only.
|
|
137
|
+
* Majors will need to go through PR to this repo.
|
|
138
|
+
*
|
|
139
|
+
* @param {import('#utils/project.js').Project} project
|
|
140
|
+
*/
|
|
141
|
+
async function upgradeDependencies(project) {
|
|
142
|
+
let existing = await packageJson.read(project.directory);
|
|
143
|
+
|
|
144
|
+
await packageJson.modify(async (json) => {
|
|
145
|
+
if (json.dependencies) {
|
|
146
|
+
Object.assign(json.dependencies, await getLatest(existing.dependencies));
|
|
147
|
+
}
|
|
148
|
+
if (json.devDependencies) {
|
|
149
|
+
Object.assign(json.devDependencies, await getLatest(existing.devDependencies));
|
|
150
|
+
}
|
|
151
|
+
}, project.directory);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The base doesn't emit a babel.config.js, but this can run over an
|
|
156
|
+
* existing project that has one -- and a JavaScript project's config
|
|
157
|
+
* must not reference the TS plugin.
|
|
158
|
+
*
|
|
159
|
+
* @param {import('#utils/project.js').Project} project
|
|
160
|
+
*/
|
|
161
|
+
async function updateBabelConfig(project) {
|
|
162
|
+
if (await project.hasOrWantsLayer("typescript")) return;
|
|
163
|
+
if (!project.hasFile("babel.config.js")) return;
|
|
164
|
+
|
|
165
|
+
await removeConfiguredPlugin(project, "@babel/plugin-transform-typescript");
|
|
166
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -10,7 +10,8 @@ import { askPath } from "./questions/path.js";
|
|
|
10
10
|
import { askIfOK } from "./questions/ok.js";
|
|
11
11
|
import { askToWrite } from "./questions/write.js";
|
|
12
12
|
import { styleText } from "node:util";
|
|
13
|
-
import { rm } from "node:fs/promises";
|
|
13
|
+
import { readFile, rm } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
14
15
|
import { Project } from "#utils/project.js";
|
|
15
16
|
import { Stage } from "#utils/stage.js";
|
|
16
17
|
import { askReplaceOrUpdate } from "./questions/replace-or-update.js";
|
|
@@ -31,7 +32,7 @@ async function main() {
|
|
|
31
32
|
const projectPath = await askPath(projectName);
|
|
32
33
|
const replaceOrUpdate = await askReplaceOrUpdate(projectPath);
|
|
33
34
|
const projectType = await askProjectType();
|
|
34
|
-
const selectedLayers = await askLayers();
|
|
35
|
+
const selectedLayers = await askLayers(projectType);
|
|
35
36
|
const packageManager = await askPackageManager();
|
|
36
37
|
|
|
37
38
|
await askIfOK();
|
|
@@ -120,9 +121,27 @@ async function main() {
|
|
|
120
121
|
|
|
121
122
|
p.log.success(`Applied ${toApply.length} change${toApply.length === 1 ? "" : "s"}`);
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Converted / generated files are written expecting the project's own
|
|
126
|
+
* formatting and lint pass to run right away (e.g. blank lines between
|
|
127
|
+
* import groups are `eslint --fix`'s job) -- so when the project has a
|
|
128
|
+
* lint:fix script, surface it as the step right after install.
|
|
129
|
+
*/
|
|
130
|
+
let lintFixStep = "";
|
|
131
|
+
try {
|
|
132
|
+
const manifest = JSON.parse(await readFile(join(projectPath, "package.json"), "utf-8"));
|
|
133
|
+
|
|
134
|
+
if (manifest.scripts?.["lint:fix"]) {
|
|
135
|
+
lintFixStep = `${packageManager} ${packageManager === "npm" ? "run " : ""}lint:fix\n`;
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// no readable package.json -- skip the suggestion
|
|
139
|
+
}
|
|
140
|
+
|
|
123
141
|
p.note(
|
|
124
142
|
`cd ${projectName}\n` +
|
|
125
143
|
`${packageManager} install\n` +
|
|
144
|
+
lintFixStep +
|
|
126
145
|
`${packageManager} ${packageManager === "npm" ? "run " : ""}start`,
|
|
127
146
|
"Next steps",
|
|
128
147
|
);
|