Skip to content

Commit 2af78ab

Browse files
authored
Merge branch 'feat/windows' into feat/windows-webpack-hmr
2 parents 21381f4 + 7168376 commit 2af78ab

6 files changed

Lines changed: 354 additions & 16 deletions

File tree

‎lib/controllers/prepare-controller.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ export class PrepareController
143143
prepareData: IPrepareData,
144144
projectData: IProjectData,
145145
): Promise<IPrepareResultData> {
146-
await this.$projectService.ensureAppResourcesExist(projectData.projectDir);
146+
await this.$projectService.ensureAppResourcesExist(
147+
projectData.projectDir,
148+
this.$mobileHelper.normalizePlatformName(prepareData.platform),
149+
);
147150
await this.$platformController.addPlatformIfNeeded(
148151
prepareData,
149152
projectData,

‎lib/definitions/project.d.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,12 @@ interface IProjectService {
9595
* Checks if App_Resources exists, or pulls down a fresh set
9696
* from the default template otherwise.
9797
* @param {string} projectDir
98+
* @param {string} platformDirName When set, also adds App_Resources/<platformDirName> from the default template if it is missing.
9899
*/
99-
ensureAppResourcesExist(projectDir: string): Promise<void>;
100+
ensureAppResourcesExist(
101+
projectDir: string,
102+
platformDirName?: string,
103+
): Promise<void>;
100104
}
101105

102106
interface INsConfigPlaform {

‎lib/services/project-service.ts‎

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ export class ProjectService implements IProjectService {
224224
}
225225

226226
@performanceLog()
227-
public async ensureAppResourcesExist(projectDir: string): Promise<void> {
227+
public async ensureAppResourcesExist(
228+
projectDir: string,
229+
platformDirName?: string
230+
): Promise<void> {
228231
const projectData = this.$projectDataService.getProjectData(projectDir);
229232
const appResourcesDestinationPath = projectData.getAppResourcesDirectoryPath(
230233
projectDir
@@ -235,23 +238,48 @@ export class ProjectService implements IProjectService {
235238
"Project does not have App_Resources - fetching from default template."
236239
);
237240
this.$fs.createDirectory(appResourcesDestinationPath);
238-
const tempDir = await this.$tempService.mkdirSync("ns-default-template");
239241
// the template installed doesn't have App_Resources -> get from a default template
240-
await this.$pacoteService.extractPackage(
241-
constants.RESERVED_TEMPLATE_NAMES["default"],
242-
tempDir
243-
);
244-
const templateProjectData = this.$projectDataService.getProjectData(
245-
tempDir
246-
);
247-
const templateAppResourcesDir = templateProjectData.getAppResourcesDirectoryPath(
248-
tempDir
249-
);
242+
const templateAppResourcesDir = await this.getDefaultTemplateAppResourcesDir();
250243
this.$fs.copyFile(
251244
path.join(templateAppResourcesDir, "*"),
252245
appResourcesDestinationPath
253246
);
247+
return;
254248
}
249+
250+
// An app created before a platform was supported (e.g. Windows) has no folder for it.
251+
if (
252+
platformDirName &&
253+
!this.$fs.exists(path.join(appResourcesDestinationPath, platformDirName))
254+
) {
255+
const templateAppResourcesDir = await this.getDefaultTemplateAppResourcesDir();
256+
const templatePlatformDir = path.join(
257+
templateAppResourcesDir,
258+
platformDirName
259+
);
260+
if (!this.$fs.exists(templatePlatformDir)) {
261+
this.$logger.warn(
262+
`App_Resources/${platformDirName} is missing and the default template does not provide one.`
263+
);
264+
return;
265+
}
266+
this.$logger.info(
267+
`Adding App_Resources/${platformDirName} from the default template.`
268+
);
269+
this.$fs.copyFile(templatePlatformDir, appResourcesDestinationPath);
270+
}
271+
}
272+
273+
private async getDefaultTemplateAppResourcesDir(): Promise<string> {
274+
const tempDir = await this.$tempService.mkdirSync("ns-default-template");
275+
await this.$pacoteService.extractPackage(
276+
constants.RESERVED_TEMPLATE_NAMES["default"],
277+
tempDir
278+
);
279+
const templateProjectData = this.$projectDataService.getProjectData(
280+
tempDir
281+
);
282+
return templateProjectData.getAppResourcesDirectoryPath(tempDir);
255283
}
256284

257285
@performanceLog()

‎lib/services/windows-project-service.ts‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,24 @@ export class WindowsProjectService
621621
}
622622
}
623623

624+
// Drop the staged files of plugins that are no longer installed (their C# sources would
625+
// otherwise still be compiled into the app). Scoped plugins stage under plugins/@scope/name.
626+
const installedNames = new Set(installedPlugins.map((p: IPluginData) => p.name));
627+
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
628+
if (!entry.isDirectory()) continue;
629+
const names = entry.name.startsWith("@")
630+
? fs
631+
.readdirSync(path.join(pluginsDir, entry.name), { withFileTypes: true })
632+
.filter((e) => e.isDirectory())
633+
.map((e) => `${entry.name}/${e.name}`)
634+
: [entry.name];
635+
for (const name of names) {
636+
if (!installedNames.has(name)) {
637+
this.$fs.deleteDirectory(path.join(pluginsDir, ...name.split("/")));
638+
}
639+
}
640+
}
641+
624642
// Write aggregate imports that the project csproj imports via "plugins\Plugins.props"
625643
const aggregatePropsPath = path.join(pluginsDir, "Plugins.props");
626644
const aggregateTargetsPath = path.join(pluginsDir, "Plugins.targets");
@@ -700,12 +718,17 @@ export class WindowsProjectService
700718
projectData.projectName,
701719
);
702720

703-
// Copy App_Resources/Windows -> platforms/windows/<Project>/App_Resources/Windows
721+
// Copy App_Resources/Windows -> platforms/windows/<Project>/App_Resources/Windows. The copy is
722+
// an exact mirror: C# sources under it are compiled into the app, so a file deleted from
723+
// App_Resources must not linger here.
704724
const destAppResourcesWindows = path.join(
705725
platformAppDir,
706726
"App_Resources",
707727
"Windows",
708728
);
729+
if (this.$fs.exists(destAppResourcesWindows)) {
730+
this.$fs.deleteDirectory(destAppResourcesWindows);
731+
}
709732
this.$fs.ensureDirectoryExists(destAppResourcesWindows);
710733

711734
const copyRecursive = (srcDir: string, destDir: string) => {
@@ -982,6 +1005,11 @@ export class WindowsProjectService
9821005
projectData.projectName,
9831006
);
9841007
const pluginStageDir = path.join(appProjectDir, "plugins", pluginData.name);
1008+
// Restage from scratch: the plugin's C# sources are compiled into the app, so files a newer
1009+
// plugin version dropped must not linger.
1010+
if (this.$fs.exists(pluginStageDir)) {
1011+
this.$fs.deleteDirectory(pluginStageDir);
1012+
}
9851013
this.$fs.ensureDirectoryExists(pluginStageDir);
9861014

9871015
// recursively copy native files (exclude JS/TS/JSON)
@@ -1028,14 +1056,19 @@ export class WindowsProjectService
10281056
);
10291057
}
10301058

1059+
// Build inputs rather than files the app ships: the plugin's C# sources (staged here,
1060+
// under the app project, so the SDK's default Compile glob builds them into the app the way
1061+
// a plugin's Java/Kotlin or Objective-C/Swift sources are on Android/iOS) and MSBuild files.
1062+
const buildInputExtensions = [".cs", ".csproj", ".props", ".targets"];
10311063
const collectStagedFiles = (root: string): string[] => {
10321064
const out: string[] = [];
10331065
if (!this.$fs.exists(root)) return out;
10341066
const walk = (dir: string) => {
10351067
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
10361068
const full = path.join(dir, e.name);
10371069
if (e.isDirectory()) walk(full);
1038-
else out.push(path.relative(root, full).split(path.sep).join("\\"));
1070+
else if (!buildInputExtensions.includes(path.extname(e.name).toLowerCase()))
1071+
out.push(path.relative(root, full).split(path.sep).join("\\"));
10391072
}
10401073
};
10411074
walk(root);

‎test/project-service.ts‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,99 @@ describe("projectService", () => {
342342
assert.isFalse(projectService.isValidNativeScriptProject("some-dir"));
343343
});
344344
});
345+
346+
describe("ensureAppResourcesExist", () => {
347+
const projectDir = path.resolve("app-resources-project");
348+
const appResources = path.join(projectDir, "App_Resources");
349+
const templateDir = path.resolve("default-template");
350+
const templateAppResources = path.join(templateDir, "App_Resources");
351+
352+
const setup = (existing: string[]) => {
353+
const exists = new Set(existing.map((p) => path.resolve(p)));
354+
const copies: { from: string; to: string }[] = [];
355+
let extracted = 0;
356+
const testInjector = new yok.Yok();
357+
testInjector.register("fs", {
358+
exists: (p: string) => exists.has(path.resolve(p)),
359+
createDirectory: (p: string) => exists.add(path.resolve(p)),
360+
copyFile: (from: string, to: string) => copies.push({ from, to }),
361+
});
362+
testInjector.register("logger", LoggerStub);
363+
testInjector.register("projectDataService", {
364+
getProjectData: (dir: string): IProjectData =>
365+
<any>{
366+
getAppResourcesDirectoryPath: (d: string) =>
367+
path.join(d === templateDir ? templateDir : dir, "App_Resources"),
368+
},
369+
});
370+
testInjector.register("pacoteService", {
371+
extractPackage: async () => {
372+
extracted++;
373+
},
374+
});
375+
testInjector.register("tempService", {
376+
mkdirSync: async () => templateDir,
377+
});
378+
for (const name of [
379+
"options",
380+
"hooksService",
381+
"packageManager",
382+
"errors",
383+
"projectConfigService",
384+
"projectHelper",
385+
"projectNameService",
386+
"projectTemplatesService",
387+
"staticConfig",
388+
"childProcess",
389+
]) {
390+
testInjector.register(name, {});
391+
}
392+
const projectService: IProjectService = testInjector.resolve(
393+
ProjectServiceLib.ProjectService
394+
);
395+
return { projectService, copies, extracted: () => extracted };
396+
};
397+
398+
it("copies the whole App_Resources when it is missing", async () => {
399+
const { projectService, copies } = setup([]);
400+
await projectService.ensureAppResourcesExist(projectDir, "Windows");
401+
assert.deepEqual(copies, [
402+
{ from: path.join(templateAppResources, "*"), to: appResources },
403+
]);
404+
});
405+
406+
it("adds a missing platform folder from the default template", async () => {
407+
const { projectService, copies } = setup([
408+
appResources,
409+
path.join(templateAppResources, "Windows"),
410+
]);
411+
await projectService.ensureAppResourcesExist(projectDir, "Windows");
412+
assert.deepEqual(copies, [
413+
{ from: path.join(templateAppResources, "Windows"), to: appResources },
414+
]);
415+
});
416+
417+
it("leaves an existing platform folder alone", async () => {
418+
const { projectService, copies, extracted } = setup([
419+
appResources,
420+
path.join(appResources, "Windows"),
421+
]);
422+
await projectService.ensureAppResourcesExist(projectDir, "Windows");
423+
assert.deepEqual(copies, []);
424+
assert.equal(extracted(), 0);
425+
});
426+
427+
it("copies nothing when the template has no folder for the platform", async () => {
428+
const { projectService, copies } = setup([appResources]);
429+
await projectService.ensureAppResourcesExist(projectDir, "Windows");
430+
assert.deepEqual(copies, []);
431+
});
432+
433+
it("does not look at platform folders without a platform", async () => {
434+
const { projectService, copies, extracted } = setup([appResources]);
435+
await projectService.ensureAppResourcesExist(projectDir);
436+
assert.deepEqual(copies, []);
437+
assert.equal(extracted(), 0);
438+
});
439+
});
345440
});

0 commit comments

Comments
 (0)