@simplysm/sd-cli 14.0.15 → 14.0.17
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/README.md +4 -3
- package/dist/angular/client-transform-stylesheet.d.ts +2 -0
- package/dist/angular/client-transform-stylesheet.d.ts.map +1 -1
- package/dist/angular/client-transform-stylesheet.js +88 -2
- package/dist/angular/client-transform-stylesheet.js.map +1 -1
- package/dist/angular/vite-angular-plugin.d.ts +7 -0
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +78 -16
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +24 -13
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +8 -9
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/device.d.ts +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +61 -12
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/lint.d.ts +0 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +2 -3
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/publish.js +3 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/replace-deps.js +1 -1
- package/dist/commands/replace-deps.js.map +1 -1
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +1 -2
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/electron/electron.d.ts +3 -2
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +54 -31
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/BaseEngine.js +1 -1
- package/dist/engines/BaseEngine.js.map +1 -1
- package/dist/engines/NgtscEngine.d.ts.map +1 -1
- package/dist/engines/NgtscEngine.js +0 -1
- package/dist/engines/NgtscEngine.js.map +1 -1
- package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
- package/dist/engines/ServerEsbuildEngine.js +0 -1
- package/dist/engines/ServerEsbuildEngine.js.map +1 -1
- package/dist/engines/TscEngine.d.ts.map +1 -1
- package/dist/engines/TscEngine.js +0 -1
- package/dist/engines/TscEngine.js.map +1 -1
- package/dist/engines/ViteEngine.d.ts.map +1 -1
- package/dist/engines/ViteEngine.js +10 -1
- package/dist/engines/ViteEngine.js.map +1 -1
- package/dist/engines/index.d.ts +0 -10
- package/dist/engines/index.d.ts.map +1 -1
- package/dist/engines/index.js +0 -5
- package/dist/engines/index.js.map +1 -1
- package/dist/engines/types.d.ts +0 -1
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/infra/SignalHandler.d.ts +1 -6
- package/dist/infra/SignalHandler.d.ts.map +1 -1
- package/dist/infra/SignalHandler.js +4 -13
- package/dist/infra/SignalHandler.js.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +7 -12
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +18 -11
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.d.ts +0 -1
- package/dist/sd-cli-entry.d.ts.map +1 -1
- package/dist/sd-cli-entry.js +13 -16
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-cli.js +1 -1
- package/dist/sd-cli.js.map +1 -1
- package/dist/sd-config.types.d.ts +12 -2
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/utils/angular-compiler.d.ts.map +1 -1
- package/dist/utils/angular-compiler.js +20 -13
- package/dist/utils/angular-compiler.js.map +1 -1
- package/dist/utils/esbuild-config.d.ts +1 -1
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +1 -4
- package/dist/utils/esbuild-config.js.map +1 -1
- package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
- package/dist/utils/ngtsc-build-core.js +3 -0
- package/dist/utils/ngtsc-build-core.js.map +1 -1
- package/dist/utils/orchestrator-utils.js +1 -1
- package/dist/utils/orchestrator-utils.js.map +1 -1
- package/dist/utils/tsc-build.d.ts +5 -0
- package/dist/utils/tsc-build.d.ts.map +1 -1
- package/dist/utils/tsc-build.js +2 -1
- package/dist/utils/tsc-build.js.map +1 -1
- package/dist/utils/vite-config.d.ts +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +22 -53
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/vite-pwa-plugin.d.ts +9 -0
- package/dist/utils/vite-pwa-plugin.d.ts.map +1 -0
- package/dist/utils/vite-pwa-plugin.js +139 -0
- package/dist/utils/vite-pwa-plugin.js.map +1 -0
- package/dist/utils/worker-utils.d.ts +2 -5
- package/dist/utils/worker-utils.d.ts.map +1 -1
- package/dist/utils/worker-utils.js +5 -11
- package/dist/utils/worker-utils.js.map +1 -1
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +9 -3
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/library-build.worker.d.ts.map +1 -1
- package/dist/workers/library-build.worker.js +6 -2
- package/dist/workers/library-build.worker.js.map +1 -1
- package/dist/workers/ngtsc-build.worker.js +2 -2
- package/dist/workers/ngtsc-build.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +6 -2
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-runtime.worker.js +4 -4
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/docs/config.md +30 -2
- package/docs/pwa-configuration-types.md +1 -1
- package/package.json +8 -10
- package/src/angular/client-transform-stylesheet.ts +104 -2
- package/src/angular/vite-angular-plugin.ts +92 -31
- package/src/capacitor/capacitor.ts +25 -26
- package/src/commands/check.ts +8 -11
- package/src/commands/device.ts +71 -17
- package/src/commands/lint.ts +2 -3
- package/src/commands/publish.ts +3 -3
- package/src/commands/replace-deps.ts +1 -1
- package/src/commands/typecheck.ts +1 -2
- package/src/electron/electron.ts +62 -43
- package/src/engines/BaseEngine.ts +1 -1
- package/src/engines/NgtscEngine.ts +0 -1
- package/src/engines/ServerEsbuildEngine.ts +0 -1
- package/src/engines/TscEngine.ts +0 -1
- package/src/engines/ViteEngine.ts +9 -1
- package/src/engines/index.ts +0 -10
- package/src/engines/types.ts +0 -1
- package/src/infra/SignalHandler.ts +4 -14
- package/src/orchestrators/BuildOrchestrator.ts +7 -9
- package/src/orchestrators/DevWatchOrchestrator.ts +22 -10
- package/src/sd-cli-entry.ts +17 -24
- package/src/sd-cli.ts +1 -1
- package/src/sd-config.types.ts +13 -2
- package/src/utils/angular-compiler.ts +21 -21
- package/src/utils/esbuild-config.ts +2 -5
- package/src/utils/ngtsc-build-core.ts +7 -0
- package/src/utils/orchestrator-utils.ts +1 -1
- package/src/utils/tsc-build.ts +7 -0
- package/src/utils/vite-config.ts +23 -55
- package/src/utils/vite-pwa-plugin.ts +168 -0
- package/src/utils/worker-utils.ts +5 -11
- package/src/workers/client.worker.ts +11 -3
- package/src/workers/library-build.worker.ts +6 -2
- package/src/workers/ngtsc-build.worker.ts +2 -2
- package/src/workers/server-build.worker.ts +7 -2
- package/src/workers/server-runtime.worker.ts +4 -4
- package/tests/angular/client-transform-stylesheet.spec.ts +43 -0
- package/tests/angular/find-affected-by-scss.spec.ts +37 -0
- package/tests/angular/fixtures/basic-app/scss/_colors.scss +1 -0
- package/tests/angular/fixtures/basic-app/scss/_variables.scss +3 -0
- package/tests/angular/fixtures/basic-app/src/styled.component.ts +14 -0
- package/tests/angular/linker-disk-cache.spec.ts +158 -0
- package/tests/angular/scss-disk-cache.spec.ts +162 -0
- package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
- package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
- package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
- package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +87 -0
- package/tests/angular/vite-angular-plugin.spec.ts +15 -15
- package/tests/capacitor/capacitor-icon.spec.ts +2 -4
- package/tests/capacitor/capacitor-init.spec.ts +2 -4
- package/tests/capacitor/capacitor-workspace.spec.ts +2 -4
- package/tests/commands/device.spec.ts +108 -8
- package/tests/commands/publish.spec.ts +2 -2
- package/tests/commands/typecheck.spec.ts +1 -1
- package/tests/electron/electron.spec.ts +24 -17
- package/tests/engines/ngtsc-engine.spec.ts +0 -3
- package/tests/engines/server-esbuild-engine.spec.ts +0 -3
- package/tests/engines/tsc-engine.spec.ts +1 -2
- package/tests/engines/vite-engine.spec.ts +0 -2
- package/tests/infra/signal-handler.spec.ts +1 -12
- package/tests/orchestrators/build-orchestrator.spec.ts +1 -7
- package/tests/orchestrators/dev-watch-orchestrator.spec.ts +24 -66
- package/tests/utils/angular-compiler.spec.ts +1396 -32
- package/tests/utils/esbuild-config.spec.ts +4 -7
- package/tests/utils/{ngtsc-build-core-angular-compiler.spec.ts → ngtsc-build-core.spec.ts} +142 -11
- package/tests/utils/orchestrator-utils.spec.ts +2 -2
- package/tests/utils/sd-config.spec.ts +2 -2
- package/tests/utils/tsc-build.spec.ts +4 -1
- package/tests/utils/vite-config.spec.ts +130 -261
- package/tests/utils/vite-pwa-plugin.acc.spec.ts +143 -0
- package/tests/utils/vite-pwa-plugin.spec.ts +350 -0
- package/tests/utils/worker-utils.spec.ts +8 -7
- package/tests/workers/client-worker.spec.ts +50 -1
- package/tests/workers/dev-port-file.verify.md +6 -0
- package/tests/workers/library-build-lint.spec.ts +1 -1
- package/tests/workers/library-build-worker.spec.ts +1 -1
- package/tests/workers/ngtsc-build-lint.spec.ts +1 -1
- package/tests/workers/server-build-lint.spec.ts +1 -1
- package/tests/workers/server-build-worker.spec.ts +1 -1
- package/tests/workers/server-runtime-worker.spec.ts +8 -1
- package/dist/infra/WorkerManager.d.ts +0 -40
- package/dist/infra/WorkerManager.d.ts.map +0 -1
- package/dist/infra/WorkerManager.js +0 -59
- package/dist/infra/WorkerManager.js.map +0 -1
- package/dist/utils/SdCliReporter.d.ts +0 -18
- package/dist/utils/SdCliReporter.d.ts.map +0 -1
- package/dist/utils/SdCliReporter.js +0 -144
- package/dist/utils/SdCliReporter.js.map +0 -1
- package/src/infra/WorkerManager.ts +0 -65
- package/src/utils/SdCliReporter.ts +0 -177
- package/tests/angular/scss-compiler-async.spec.ts +0 -54
- package/tests/commands/dev.spec.ts +0 -53
- package/tests/commands/watch.spec.ts +0 -53
- package/tests/infra/worker-manager.spec.ts +0 -63
- package/tests/utils/angular-compiler-emit.spec.ts +0 -570
- package/tests/utils/angular-compiler-init.spec.ts +0 -705
- package/tests/utils/angular-compiler-update.spec.ts +0 -293
- package/tests/utils/build-env.spec.ts +0 -33
- package/tests/utils/ngtsc-build-core-transform-stylesheet.spec.ts +0 -124
- package/tests/utils/ngtsc-scss-refactor.spec.ts +0 -47
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { PluginOption, ResolvedConfig } from "vite";
|
|
2
|
+
import type { SdPwaConfig } from "../sd-config.types.js";
|
|
3
|
+
import { generatePwaIcons } from "./generate-pwa-icons.js";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { glob } from "glob";
|
|
7
|
+
|
|
8
|
+
export interface SdPwaPluginOptions {
|
|
9
|
+
pkgDir: string;
|
|
10
|
+
pkgName: string;
|
|
11
|
+
pwa?: SdPwaConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function sdPwaPlugin(options: SdPwaPluginOptions): PluginOption {
|
|
15
|
+
const pwaConfig = options.pwa ?? {};
|
|
16
|
+
let resolvedBase: string;
|
|
17
|
+
let resolvedOutDir: string;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
name: "sd-pwa",
|
|
21
|
+
|
|
22
|
+
configResolved(config: ResolvedConfig) {
|
|
23
|
+
resolvedBase = config.base;
|
|
24
|
+
resolvedOutDir = config.build.outDir;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
transformIndexHtml() {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
tag: "link",
|
|
31
|
+
attrs: { rel: "manifest", href: "manifest.webmanifest" },
|
|
32
|
+
injectTo: "head" as const,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
tag: "script",
|
|
36
|
+
children: generateRegistrationScript(),
|
|
37
|
+
injectTo: "body" as const,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async closeBundle() {
|
|
43
|
+
// Read version from package.json
|
|
44
|
+
const pkgJsonPath = path.join(options.pkgDir, "package.json");
|
|
45
|
+
const pkgJson = JSON.parse(
|
|
46
|
+
fs.readFileSync(pkgJsonPath, "utf-8"),
|
|
47
|
+
) as Record<string, unknown>;
|
|
48
|
+
const version = pkgJson["version"] as string;
|
|
49
|
+
|
|
50
|
+
// Icons
|
|
51
|
+
let iconsField: Record<string, unknown> = {};
|
|
52
|
+
if (pwaConfig.manifest?.icons != null) {
|
|
53
|
+
iconsField = { icons: pwaConfig.manifest.icons };
|
|
54
|
+
} else {
|
|
55
|
+
const generated = await generatePwaIcons(options.pkgDir);
|
|
56
|
+
if (generated.length > 0) {
|
|
57
|
+
iconsField = { icons: generated };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Manifest
|
|
62
|
+
const manifest = {
|
|
63
|
+
name: pwaConfig.manifest?.name ?? options.pkgName,
|
|
64
|
+
short_name: pwaConfig.manifest?.short_name ?? options.pkgName,
|
|
65
|
+
display: pwaConfig.manifest?.display ?? "standalone",
|
|
66
|
+
theme_color: pwaConfig.manifest?.theme_color ?? "#ffffff",
|
|
67
|
+
background_color: pwaConfig.manifest?.background_color ?? "#ffffff",
|
|
68
|
+
start_url: ".",
|
|
69
|
+
scope: ".",
|
|
70
|
+
...iconsField,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(resolvedOutDir, "manifest.webmanifest"),
|
|
75
|
+
JSON.stringify(manifest, null, 2),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Precache file list
|
|
79
|
+
const globPatterns = pwaConfig.workbox?.globPatterns ?? [
|
|
80
|
+
"**/*.{js,css,html,ico,png,svg,woff2}",
|
|
81
|
+
];
|
|
82
|
+
const globResults = await Promise.all(
|
|
83
|
+
globPatterns.map((pattern) => glob(pattern, { cwd: resolvedOutDir })),
|
|
84
|
+
);
|
|
85
|
+
const precacheUrls = [...new Set(globResults.flat())]
|
|
86
|
+
.filter((f) => f !== "sw.js" && f !== "manifest.webmanifest")
|
|
87
|
+
.map((f) => f.replace(/\\/g, "/"));
|
|
88
|
+
|
|
89
|
+
// Service Worker
|
|
90
|
+
const swContent = generateSwContent(version, resolvedBase, precacheUrls);
|
|
91
|
+
fs.writeFileSync(path.join(resolvedOutDir, "sw.js"), swContent);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function generateRegistrationScript(): string {
|
|
97
|
+
return `(function(){
|
|
98
|
+
if(!("serviceWorker" in navigator))return;
|
|
99
|
+
navigator.serviceWorker.register("sw.js").then(function(reg){
|
|
100
|
+
if(reg.waiting){d(reg.waiting);return}
|
|
101
|
+
reg.addEventListener("updatefound",function(){
|
|
102
|
+
var w=reg.installing;
|
|
103
|
+
if(!w)return;
|
|
104
|
+
w.addEventListener("statechange",function(){
|
|
105
|
+
if(w.state==="installed"&&navigator.serviceWorker.controller)d(w);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
var r=false;
|
|
110
|
+
navigator.serviceWorker.addEventListener("controllerchange",function(){
|
|
111
|
+
if(!r){r=true;window.location.reload()}
|
|
112
|
+
});
|
|
113
|
+
function d(w){
|
|
114
|
+
window.dispatchEvent(new CustomEvent("sd-pwa-update-ready",{
|
|
115
|
+
detail:{update:function(){w.postMessage({type:"SKIP_WAITING"})}}
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
})();`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function generateSwContent(
|
|
122
|
+
version: string,
|
|
123
|
+
base: string,
|
|
124
|
+
precacheUrls: string[],
|
|
125
|
+
): string {
|
|
126
|
+
const urlsArray = JSON.stringify(precacheUrls, null, 2);
|
|
127
|
+
return `const APP_VERSION = ${JSON.stringify(version)};
|
|
128
|
+
const CACHE_NAME = "precache-" + APP_VERSION;
|
|
129
|
+
const BASE_URL = ${JSON.stringify(base)};
|
|
130
|
+
const PRECACHE_URLS = ${urlsArray};
|
|
131
|
+
|
|
132
|
+
self.addEventListener("install", (event) => {
|
|
133
|
+
event.waitUntil(
|
|
134
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
self.addEventListener("activate", (event) => {
|
|
139
|
+
event.waitUntil(
|
|
140
|
+
caches.keys().then((names) =>
|
|
141
|
+
Promise.all(
|
|
142
|
+
names
|
|
143
|
+
.filter((name) => name.startsWith("precache-") && name !== CACHE_NAME)
|
|
144
|
+
.map((name) => caches.delete(name))
|
|
145
|
+
)
|
|
146
|
+
).then(() => self.clients.claim())
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
self.addEventListener("fetch", (event) => {
|
|
151
|
+
event.respondWith(
|
|
152
|
+
caches.match(event.request).then((cached) => {
|
|
153
|
+
if (cached) return cached;
|
|
154
|
+
if (event.request.mode === "navigate") {
|
|
155
|
+
return caches.match(BASE_URL + "index.html").then((resp) => resp || fetch(event.request));
|
|
156
|
+
}
|
|
157
|
+
return fetch(event.request);
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
self.addEventListener("message", (event) => {
|
|
163
|
+
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
164
|
+
self.skipWaiting();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { type ConsolaInstance } from "consola";
|
|
2
|
+
import { setupConsola } from "@simplysm/core-node";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* 워커 스레드에서
|
|
6
|
-
*
|
|
7
|
-
* SD_DEBUG 환경변수(메인 프로세스의 --debug 플래그로 설정)를 확인하고
|
|
8
|
-
* 현재 워커 스레드의 consola에 디버그 로그 레벨을 적용한다.
|
|
5
|
+
* 워커 스레드에서 consola를 설정한다.
|
|
9
6
|
* 워커 모듈 최상위에서 호출해야 한다.
|
|
10
7
|
*/
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
if (process.env["SD_DEBUG"] === "true") {
|
|
14
|
-
consola.level = LogLevels.debug;
|
|
15
|
-
}
|
|
8
|
+
export function setupWorkerConsola(): void {
|
|
9
|
+
setupConsola({ cli: true });
|
|
16
10
|
}
|
|
17
11
|
|
|
18
12
|
/**
|
|
@@ -6,13 +6,13 @@ import mime from "mime";
|
|
|
6
6
|
import { createWorker } from "@simplysm/core-node";
|
|
7
7
|
import { err as errNs } from "@simplysm/core-common";
|
|
8
8
|
import { consola } from "consola";
|
|
9
|
-
import { registerCleanupHandlers,
|
|
9
|
+
import { registerCleanupHandlers, setupWorkerConsola } from "../utils/worker-utils.js";
|
|
10
10
|
import { createClientViteConfig } from "../utils/vite-config.js";
|
|
11
11
|
import type { ScopeWatchReplaceDep } from "../utils/vite-scope-watch-plugin.js";
|
|
12
12
|
import type { SdBrowserSupportConfig, SdPwaConfig } from "../sd-config.types.js";
|
|
13
13
|
import type { LintWithProgramResult } from "../utils/lint-with-program.js";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
setupWorkerConsola();
|
|
16
16
|
|
|
17
17
|
//#region Types
|
|
18
18
|
|
|
@@ -247,8 +247,13 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
247
247
|
|
|
248
248
|
sender.send("serverReady", { port: actualPort });
|
|
249
249
|
|
|
250
|
+
// .dev-port 기록 (device 명령어에서 자동 탐지용)
|
|
251
|
+
const distDir = path.join(info.pkgDir, "dist");
|
|
252
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
253
|
+
fs.writeFileSync(path.join(distDir, ".dev-port"), String(actualPort));
|
|
254
|
+
|
|
250
255
|
// .config.json 생성
|
|
251
|
-
writeConfigJson(
|
|
256
|
+
writeConfigJson(distDir, info.configs);
|
|
252
257
|
|
|
253
258
|
return { success: true };
|
|
254
259
|
} catch (err) {
|
|
@@ -323,6 +328,9 @@ async function startLegacyWatch(info: ClientBuildInfo): Promise<ClientBuildResul
|
|
|
323
328
|
|
|
324
329
|
sender.send("serverReady", { port: serverPort });
|
|
325
330
|
|
|
331
|
+
// .dev-port 기록 (device 명령어에서 자동 탐지용)
|
|
332
|
+
fs.writeFileSync(path.join(info.pkgDir, "dist", ".dev-port"), String(serverPort));
|
|
333
|
+
|
|
326
334
|
// 첫 빌드 완료 대기
|
|
327
335
|
return await new Promise<ClientBuildResult>((resolve) => {
|
|
328
336
|
let firstBuildResolved = false;
|
|
@@ -9,9 +9,9 @@ import type { LintWithProgramResult } from "../utils/lint-with-program";
|
|
|
9
9
|
import { runTscPackageBuild } from "../utils/tsc-build";
|
|
10
10
|
import { LintWithProgramRunner } from "../utils/lint-with-program";
|
|
11
11
|
import { collectDeps } from "../utils/package-utils";
|
|
12
|
-
import { registerCleanupHandlers, createOnceGuard,
|
|
12
|
+
import { registerCleanupHandlers, createOnceGuard, setupWorkerConsola } from "../utils/worker-utils";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
setupWorkerConsola();
|
|
15
15
|
|
|
16
16
|
//#region Types
|
|
17
17
|
|
|
@@ -53,6 +53,7 @@ async function cleanup(): Promise<void> {
|
|
|
53
53
|
const watcherToClose = fsWatcher;
|
|
54
54
|
fsWatcher = undefined;
|
|
55
55
|
lastSourceFilePaths = undefined;
|
|
56
|
+
lastBuilderProgram = undefined;
|
|
56
57
|
await watcherToClose?.close();
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -106,6 +107,7 @@ const guardStartWatch = createOnceGuard("startWatch");
|
|
|
106
107
|
let watchInfo: LibraryBuildInfo | undefined;
|
|
107
108
|
let watchLintRunner: LintWithProgramRunner | undefined;
|
|
108
109
|
let lastSourceFilePaths: Set<string> | undefined;
|
|
110
|
+
let lastBuilderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
|
|
109
111
|
|
|
110
112
|
function extractSourceFilePaths(program: ts.Program | undefined): Set<string> | undefined {
|
|
111
113
|
if (program == null) return undefined;
|
|
@@ -126,7 +128,9 @@ async function rebuildAll(): Promise<CombinedBuildEvent> {
|
|
|
126
128
|
output: info.output,
|
|
127
129
|
env: info.output.env,
|
|
128
130
|
includeTests: info.output.includeTests,
|
|
131
|
+
oldBuilderProgram: lastBuilderProgram,
|
|
129
132
|
});
|
|
133
|
+
lastBuilderProgram = tscResult.builderProgram ?? lastBuilderProgram;
|
|
130
134
|
|
|
131
135
|
// 의존성 필터링을 위한 소스 파일 경로 업데이트
|
|
132
136
|
lastSourceFilePaths = extractSourceFilePaths(tscResult.program) ?? lastSourceFilePaths;
|
|
@@ -3,7 +3,7 @@ import ts from "typescript";
|
|
|
3
3
|
import { createWorker, FsWatcher, pathx } from "@simplysm/core-node";
|
|
4
4
|
import { err as errNs } from "@simplysm/core-common";
|
|
5
5
|
import { consola } from "consola";
|
|
6
|
-
import { registerCleanupHandlers, createOnceGuard,
|
|
6
|
+
import { registerCleanupHandlers, createOnceGuard, setupWorkerConsola } from "../utils/worker-utils";
|
|
7
7
|
import {
|
|
8
8
|
runNgtscBuild,
|
|
9
9
|
buildCompilerOptions,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { AngularCompiler, AngularSourceFileCache } from "../utils/angular-compiler";
|
|
29
29
|
import { collectDeps } from "../utils/package-utils";
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
setupWorkerConsola();
|
|
32
32
|
|
|
33
33
|
//#region 타입 (워커 인터페이스용 re-export)
|
|
34
34
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
1
2
|
import path from "path";
|
|
2
3
|
import fs from "fs";
|
|
3
4
|
import esbuild from "esbuild";
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
} from "../utils/esbuild-config";
|
|
19
20
|
import { runTscPackageBuild } from "../utils/tsc-build";
|
|
20
21
|
import { LintWithProgramRunner } from "../utils/lint-with-program";
|
|
21
|
-
import { registerCleanupHandlers, createOnceGuard,
|
|
22
|
+
import { registerCleanupHandlers, createOnceGuard, setupWorkerConsola } from "../utils/worker-utils";
|
|
22
23
|
import { collectDeps } from "../utils/package-utils";
|
|
23
24
|
import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public";
|
|
24
25
|
|
|
@@ -96,7 +97,7 @@ export interface ServerBuildWorkerEvents extends Record<string, unknown> {
|
|
|
96
97
|
|
|
97
98
|
//#region Resource Management
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
setupWorkerConsola();
|
|
100
101
|
|
|
101
102
|
const logger = consola.withTag("sd:cli:server-build:worker");
|
|
102
103
|
|
|
@@ -116,6 +117,7 @@ async function cleanup(): Promise<void> {
|
|
|
116
117
|
const contextToDispose = esbuildContext;
|
|
117
118
|
esbuildContext = undefined;
|
|
118
119
|
lastMetafile = undefined;
|
|
120
|
+
lastBuilderProgram = undefined;
|
|
119
121
|
|
|
120
122
|
const watcherToClose = publicWatcher;
|
|
121
123
|
publicWatcher = undefined;
|
|
@@ -427,6 +429,7 @@ const guardStartWatch = createOnceGuard("startWatch");
|
|
|
427
429
|
// watch 모드용 가변 상태
|
|
428
430
|
let watchInfo: ServerWatchInfo | undefined;
|
|
429
431
|
let watchLintRunner: LintWithProgramRunner | undefined;
|
|
432
|
+
let lastBuilderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
|
|
430
433
|
|
|
431
434
|
/**
|
|
432
435
|
* esbuild + tsc 병렬 리빌드 (watch 모드)
|
|
@@ -467,7 +470,9 @@ async function rebuildAll(): Promise<ServerCombinedBuildEvent> {
|
|
|
467
470
|
parsedConfig,
|
|
468
471
|
env: info.output.env,
|
|
469
472
|
includeTests: info.output.includeTests,
|
|
473
|
+
oldBuilderProgram: lastBuilderProgram,
|
|
470
474
|
});
|
|
475
|
+
lastBuilderProgram = tscResult.builderProgram ?? lastBuilderProgram;
|
|
471
476
|
|
|
472
477
|
// lint 실행 (활성화 + program 사용 가능 시)
|
|
473
478
|
let lint: LintWithProgramResult | undefined;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createWorker } from "@simplysm/core-node";
|
|
2
|
-
import { err as errNs } from "@simplysm/core-common";
|
|
2
|
+
import { env, err as errNs } from "@simplysm/core-common";
|
|
3
3
|
import { consola } from "consola";
|
|
4
4
|
import proxy from "@fastify/http-proxy";
|
|
5
5
|
import net from "net";
|
|
6
6
|
import { pathToFileURL } from "url";
|
|
7
|
-
import { registerCleanupHandlers,
|
|
7
|
+
import { registerCleanupHandlers, setupWorkerConsola } from "../utils/worker-utils";
|
|
8
8
|
|
|
9
9
|
//#region Types
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ export interface ServerRuntimeWorkerEvents extends Record<string, unknown> {
|
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
setupWorkerConsola();
|
|
46
46
|
|
|
47
47
|
const logger = consola.withTag("sd:cli:server-runtime:worker");
|
|
48
48
|
|
|
@@ -122,7 +122,7 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
|
|
|
122
122
|
// main.js import 전에 환경변수를 process.env에 주입
|
|
123
123
|
if (info.env != null) {
|
|
124
124
|
for (const [key, value] of Object.entries(info.env)) {
|
|
125
|
-
|
|
125
|
+
env(key, value);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -3,6 +3,7 @@ import path from "path";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import { createClientTransformStylesheet } from "../../src/angular/client-transform-stylesheet.js";
|
|
6
|
+
import { compileScssFileAsync, compileScssStringAsync } from "../../src/utils/scss-compiler.js";
|
|
6
7
|
|
|
7
8
|
const TMP_DIR = path.join(os.tmpdir(), "sd-cli-stylesheet-test");
|
|
8
9
|
|
|
@@ -151,3 +152,45 @@ describe("createClientTransformStylesheet", () => {
|
|
|
151
152
|
expect(deps.size).toBeGreaterThan(0);
|
|
152
153
|
});
|
|
153
154
|
});
|
|
155
|
+
|
|
156
|
+
// ─── scss-compiler async (low-level) ───
|
|
157
|
+
|
|
158
|
+
describe("scss-compiler async", () => {
|
|
159
|
+
// Scenario: 외부 .scss 파일 변환
|
|
160
|
+
it("compiles external .scss file asynchronously", async () => {
|
|
161
|
+
ensureTmpDir();
|
|
162
|
+
const scssPath = path.join(TMP_DIR, "test.scss");
|
|
163
|
+
fs.writeFileSync(scssPath, "$color: red;\n.host { color: $color; }");
|
|
164
|
+
|
|
165
|
+
const result = await compileScssFileAsync(scssPath, []);
|
|
166
|
+
|
|
167
|
+
expect(result.css).toContain("color: red");
|
|
168
|
+
expect(result.css).not.toContain("$color");
|
|
169
|
+
expect(result.dependencies).toBeInstanceOf(Array);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Scenario: 인라인 SCSS 문자열 변환
|
|
173
|
+
it("compiles inline SCSS string asynchronously", async () => {
|
|
174
|
+
ensureTmpDir();
|
|
175
|
+
const containingFile = path.join(TMP_DIR, "component.ts");
|
|
176
|
+
|
|
177
|
+
const result = await compileScssStringAsync(
|
|
178
|
+
"$size: 16px;\n.text { font-size: $size; }",
|
|
179
|
+
containingFile,
|
|
180
|
+
[],
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(result.css).toContain("font-size: 16px");
|
|
184
|
+
expect(result.css).not.toContain("$size");
|
|
185
|
+
expect(result.dependencies).toBeInstanceOf(Array);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Scenario: SCSS 컴파일 에러 시 에러
|
|
189
|
+
it("throws on invalid SCSS syntax", async () => {
|
|
190
|
+
ensureTmpDir();
|
|
191
|
+
const scssPath = path.join(TMP_DIR, "invalid.scss");
|
|
192
|
+
fs.writeFileSync(scssPath, ".host { @include nonexistent-mixin(); }");
|
|
193
|
+
|
|
194
|
+
await expect(compileScssFileAsync(scssPath, [])).rejects.toThrow();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { findAffectedByScss } from "../../src/angular/vite-angular-plugin.js";
|
|
3
|
+
|
|
4
|
+
describe("findAffectedByScss", () => {
|
|
5
|
+
it("returns owner file when SCSS is in its dependency set", () => {
|
|
6
|
+
const deps = new Map<string, Set<string>>();
|
|
7
|
+
deps.set("/app/src/comp.ts", new Set(["/app/scss/_vars.scss"]));
|
|
8
|
+
|
|
9
|
+
const result = findAffectedByScss("/app/scss/_vars.scss", deps);
|
|
10
|
+
expect(result).toEqual(["/app/src/comp.ts"]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns multiple owners when multiple components depend on the same SCSS", () => {
|
|
14
|
+
const deps = new Map<string, Set<string>>();
|
|
15
|
+
deps.set("/app/src/comp-a.ts", new Set(["/app/scss/_vars.scss"]));
|
|
16
|
+
deps.set("/app/src/comp-b.ts", new Set(["/app/scss/_vars.scss", "/app/scss/_extra.scss"]));
|
|
17
|
+
deps.set("/app/src/comp-c.ts", new Set(["/app/scss/_other.scss"]));
|
|
18
|
+
|
|
19
|
+
const result = findAffectedByScss("/app/scss/_vars.scss", deps);
|
|
20
|
+
expect(result).toEqual(expect.arrayContaining(["/app/src/comp-a.ts", "/app/src/comp-b.ts"]));
|
|
21
|
+
expect(result).not.toContain("/app/src/comp-c.ts");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns empty array when no component depends on the SCSS", () => {
|
|
25
|
+
const deps = new Map<string, Set<string>>();
|
|
26
|
+
deps.set("/app/src/comp.ts", new Set(["/app/scss/_vars.scss"]));
|
|
27
|
+
|
|
28
|
+
const result = findAffectedByScss("/app/scss/_unknown.scss", deps);
|
|
29
|
+
expect(result).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns empty array when scssDependencies is empty", () => {
|
|
33
|
+
const deps = new Map<string, Set<string>>();
|
|
34
|
+
const result = findAffectedByScss("/app/scss/_vars.scss", deps);
|
|
35
|
+
expect(result).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$base: blue;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Component } from "@angular/core";
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: "app-styled",
|
|
5
|
+
standalone: true,
|
|
6
|
+
template: `<p>styled</p>`,
|
|
7
|
+
styles: [`
|
|
8
|
+
@use 'variables' as vars;
|
|
9
|
+
:host {
|
|
10
|
+
color: vars.$primary;
|
|
11
|
+
}
|
|
12
|
+
`],
|
|
13
|
+
})
|
|
14
|
+
export class StyledComponent {}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
|
|
6
|
+
const mockTransformFile = vi.fn(() => Promise.resolve("transformed-code"));
|
|
7
|
+
|
|
8
|
+
vi.mock("@angular/build/private", () => ({
|
|
9
|
+
JavaScriptTransformer: class MockJSTransformer {
|
|
10
|
+
transformFile = mockTransformFile;
|
|
11
|
+
transformData = vi.fn(() => Promise.resolve(new TextEncoder().encode("transformed")));
|
|
12
|
+
close = vi.fn(() => Promise.resolve());
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const { sdAngularPlugin } = await import("../../src/angular/vite-angular-plugin.js");
|
|
17
|
+
|
|
18
|
+
const TMP_DIR = path.join(os.tmpdir(), "sd-cli-linker-cache-test");
|
|
19
|
+
const CACHE_DIR = path.join(TMP_DIR, "cache");
|
|
20
|
+
|
|
21
|
+
type LoadHandler = (id: string) => Promise<string | null>;
|
|
22
|
+
|
|
23
|
+
function getLoadHandler(plugin: ReturnType<typeof sdAngularPlugin>): LoadHandler {
|
|
24
|
+
const config = (plugin as any).config();
|
|
25
|
+
const rolldownPlugin = config.optimizeDeps.rolldownOptions.plugins[0] as {
|
|
26
|
+
load: (id: string) => Promise<string | null>;
|
|
27
|
+
};
|
|
28
|
+
return (id: string) => rolldownPlugin.load(id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("Linker disk cache (optimizeDeps)", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
fs.mkdirSync(TMP_DIR, { recursive: true });
|
|
34
|
+
mockTransformFile.mockClear();
|
|
35
|
+
mockTransformFile.mockResolvedValue("transformed-code");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
fs.rmSync(TMP_DIR, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Acceptance: cache miss → transform + store, cache hit → load from disk
|
|
43
|
+
it("caches transformFile result on miss and returns cached on hit", async () => {
|
|
44
|
+
const jsFile = path.join(TMP_DIR, "module.js");
|
|
45
|
+
fs.writeFileSync(jsFile, "export const x = 1;");
|
|
46
|
+
|
|
47
|
+
const plugin = sdAngularPlugin({
|
|
48
|
+
tsconfig: path.join(TMP_DIR, "tsconfig.json"),
|
|
49
|
+
dev: true,
|
|
50
|
+
linkerCacheDir: CACHE_DIR,
|
|
51
|
+
});
|
|
52
|
+
const load = getLoadHandler(plugin);
|
|
53
|
+
|
|
54
|
+
// First call: cache miss
|
|
55
|
+
const result1 = await load(jsFile);
|
|
56
|
+
expect(mockTransformFile).toHaveBeenCalledOnce();
|
|
57
|
+
expect(result1).toBe("transformed-code");
|
|
58
|
+
|
|
59
|
+
// Cache file created
|
|
60
|
+
const cacheFiles = fs.readdirSync(CACHE_DIR);
|
|
61
|
+
expect(cacheFiles.length).toBe(1);
|
|
62
|
+
expect(cacheFiles[0]).toMatch(/^[a-f0-9]+\.js$/);
|
|
63
|
+
|
|
64
|
+
// Second call: cache hit
|
|
65
|
+
mockTransformFile.mockClear();
|
|
66
|
+
const result2 = await load(jsFile);
|
|
67
|
+
expect(mockTransformFile).not.toHaveBeenCalled();
|
|
68
|
+
expect(result2).toBe("transformed-code");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Acceptance: file content change → cache miss
|
|
72
|
+
it("invalidates cache when file content changes", async () => {
|
|
73
|
+
const jsFile = path.join(TMP_DIR, "changing.js");
|
|
74
|
+
fs.writeFileSync(jsFile, "export const v = 1;");
|
|
75
|
+
|
|
76
|
+
const plugin = sdAngularPlugin({
|
|
77
|
+
tsconfig: path.join(TMP_DIR, "tsconfig.json"),
|
|
78
|
+
dev: true,
|
|
79
|
+
linkerCacheDir: CACHE_DIR,
|
|
80
|
+
});
|
|
81
|
+
const load = getLoadHandler(plugin);
|
|
82
|
+
|
|
83
|
+
await load(jsFile);
|
|
84
|
+
expect(mockTransformFile).toHaveBeenCalledOnce();
|
|
85
|
+
|
|
86
|
+
// Change content
|
|
87
|
+
fs.writeFileSync(jsFile, "export const v = 2;");
|
|
88
|
+
mockTransformFile.mockClear();
|
|
89
|
+
mockTransformFile.mockResolvedValue("transformed-v2");
|
|
90
|
+
|
|
91
|
+
const result = await load(jsFile);
|
|
92
|
+
expect(mockTransformFile).toHaveBeenCalledOnce();
|
|
93
|
+
expect(result).toBe("transformed-v2");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Unit: corrupted cache file → graceful fallback
|
|
97
|
+
it("falls back to transform when cache file is corrupted", async () => {
|
|
98
|
+
const jsFile = path.join(TMP_DIR, "fallback.js");
|
|
99
|
+
fs.writeFileSync(jsFile, "export const y = 1;");
|
|
100
|
+
|
|
101
|
+
const plugin = sdAngularPlugin({
|
|
102
|
+
tsconfig: path.join(TMP_DIR, "tsconfig.json"),
|
|
103
|
+
dev: true,
|
|
104
|
+
linkerCacheDir: CACHE_DIR,
|
|
105
|
+
});
|
|
106
|
+
const load = getLoadHandler(plugin);
|
|
107
|
+
|
|
108
|
+
// First call to populate cache
|
|
109
|
+
await load(jsFile);
|
|
110
|
+
|
|
111
|
+
// Remove the cache file to simulate corruption/missing cache
|
|
112
|
+
const cacheFiles = fs.readdirSync(CACHE_DIR);
|
|
113
|
+
fs.rmSync(path.join(CACHE_DIR, cacheFiles[0]));
|
|
114
|
+
|
|
115
|
+
mockTransformFile.mockClear();
|
|
116
|
+
const result = await load(jsFile);
|
|
117
|
+
expect(mockTransformFile).toHaveBeenCalledOnce();
|
|
118
|
+
expect(result).toBe("transformed-code");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Unit: Uint8Array result from transformFile is handled
|
|
122
|
+
it("handles Uint8Array result from transformFile", async () => {
|
|
123
|
+
const jsFile = path.join(TMP_DIR, "uint8.js");
|
|
124
|
+
fs.writeFileSync(jsFile, "export const z = 1;");
|
|
125
|
+
|
|
126
|
+
mockTransformFile.mockResolvedValue(new TextEncoder().encode("uint8-transformed") as unknown as string);
|
|
127
|
+
|
|
128
|
+
const plugin = sdAngularPlugin({
|
|
129
|
+
tsconfig: path.join(TMP_DIR, "tsconfig.json"),
|
|
130
|
+
dev: true,
|
|
131
|
+
linkerCacheDir: CACHE_DIR,
|
|
132
|
+
});
|
|
133
|
+
const load = getLoadHandler(plugin);
|
|
134
|
+
|
|
135
|
+
const result = await load(jsFile);
|
|
136
|
+
expect(result).toBe("uint8-transformed");
|
|
137
|
+
|
|
138
|
+
// Second call: cache hit should also return string
|
|
139
|
+
mockTransformFile.mockClear();
|
|
140
|
+
const result2 = await load(jsFile);
|
|
141
|
+
expect(mockTransformFile).not.toHaveBeenCalled();
|
|
142
|
+
expect(result2).toBe("uint8-transformed");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Unit: non-.js file returns null (filter)
|
|
146
|
+
it("returns null for non-js files", async () => {
|
|
147
|
+
const plugin = sdAngularPlugin({
|
|
148
|
+
tsconfig: path.join(TMP_DIR, "tsconfig.json"),
|
|
149
|
+
dev: true,
|
|
150
|
+
linkerCacheDir: CACHE_DIR,
|
|
151
|
+
});
|
|
152
|
+
const load = getLoadHandler(plugin);
|
|
153
|
+
|
|
154
|
+
const result = await load(path.join(TMP_DIR, "data.json"));
|
|
155
|
+
expect(result).toBeNull();
|
|
156
|
+
expect(mockTransformFile).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|