diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 5d687cd933..cbf234616a 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -143,7 +143,10 @@ export class PrepareController prepareData: IPrepareData, projectData: IProjectData, ): Promise { - await this.$projectService.ensureAppResourcesExist(projectData.projectDir); + await this.$projectService.ensureAppResourcesExist( + projectData.projectDir, + this.$mobileHelper.normalizePlatformName(prepareData.platform), + ); await this.$platformController.addPlatformIfNeeded( prepareData, projectData, diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 67f1a8b087..d013d42b07 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -95,8 +95,12 @@ interface IProjectService { * Checks if App_Resources exists, or pulls down a fresh set * from the default template otherwise. * @param {string} projectDir + * @param {string} platformDirName When set, also adds App_Resources/ from the default template if it is missing. */ - ensureAppResourcesExist(projectDir: string): Promise; + ensureAppResourcesExist( + projectDir: string, + platformDirName?: string, + ): Promise; } interface INsConfigPlaform { diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index 91bc4bce84..0ab29c55c8 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -224,7 +224,10 @@ export class ProjectService implements IProjectService { } @performanceLog() - public async ensureAppResourcesExist(projectDir: string): Promise { + public async ensureAppResourcesExist( + projectDir: string, + platformDirName?: string + ): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); const appResourcesDestinationPath = projectData.getAppResourcesDirectoryPath( projectDir @@ -235,23 +238,48 @@ export class ProjectService implements IProjectService { "Project does not have App_Resources - fetching from default template." ); this.$fs.createDirectory(appResourcesDestinationPath); - const tempDir = await this.$tempService.mkdirSync("ns-default-template"); // the template installed doesn't have App_Resources -> get from a default template - await this.$pacoteService.extractPackage( - constants.RESERVED_TEMPLATE_NAMES["default"], - tempDir - ); - const templateProjectData = this.$projectDataService.getProjectData( - tempDir - ); - const templateAppResourcesDir = templateProjectData.getAppResourcesDirectoryPath( - tempDir - ); + const templateAppResourcesDir = await this.getDefaultTemplateAppResourcesDir(); this.$fs.copyFile( path.join(templateAppResourcesDir, "*"), appResourcesDestinationPath ); + return; } + + // An app created before a platform was supported (e.g. Windows) has no folder for it. + if ( + platformDirName && + !this.$fs.exists(path.join(appResourcesDestinationPath, platformDirName)) + ) { + const templateAppResourcesDir = await this.getDefaultTemplateAppResourcesDir(); + const templatePlatformDir = path.join( + templateAppResourcesDir, + platformDirName + ); + if (!this.$fs.exists(templatePlatformDir)) { + this.$logger.warn( + `App_Resources/${platformDirName} is missing and the default template does not provide one.` + ); + return; + } + this.$logger.info( + `Adding App_Resources/${platformDirName} from the default template.` + ); + this.$fs.copyFile(templatePlatformDir, appResourcesDestinationPath); + } + } + + private async getDefaultTemplateAppResourcesDir(): Promise { + const tempDir = await this.$tempService.mkdirSync("ns-default-template"); + await this.$pacoteService.extractPackage( + constants.RESERVED_TEMPLATE_NAMES["default"], + tempDir + ); + const templateProjectData = this.$projectDataService.getProjectData( + tempDir + ); + return templateProjectData.getAppResourcesDirectoryPath(tempDir); } @performanceLog() diff --git a/test/project-service.ts b/test/project-service.ts index 21d1e5f1dc..dadc3349ad 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -342,4 +342,99 @@ describe("projectService", () => { assert.isFalse(projectService.isValidNativeScriptProject("some-dir")); }); }); + + describe("ensureAppResourcesExist", () => { + const projectDir = path.resolve("app-resources-project"); + const appResources = path.join(projectDir, "App_Resources"); + const templateDir = path.resolve("default-template"); + const templateAppResources = path.join(templateDir, "App_Resources"); + + const setup = (existing: string[]) => { + const exists = new Set(existing.map((p) => path.resolve(p))); + const copies: { from: string; to: string }[] = []; + let extracted = 0; + const testInjector = new yok.Yok(); + testInjector.register("fs", { + exists: (p: string) => exists.has(path.resolve(p)), + createDirectory: (p: string) => exists.add(path.resolve(p)), + copyFile: (from: string, to: string) => copies.push({ from, to }), + }); + testInjector.register("logger", LoggerStub); + testInjector.register("projectDataService", { + getProjectData: (dir: string): IProjectData => + { + getAppResourcesDirectoryPath: (d: string) => + path.join(d === templateDir ? templateDir : dir, "App_Resources"), + }, + }); + testInjector.register("pacoteService", { + extractPackage: async () => { + extracted++; + }, + }); + testInjector.register("tempService", { + mkdirSync: async () => templateDir, + }); + for (const name of [ + "options", + "hooksService", + "packageManager", + "errors", + "projectConfigService", + "projectHelper", + "projectNameService", + "projectTemplatesService", + "staticConfig", + "childProcess", + ]) { + testInjector.register(name, {}); + } + const projectService: IProjectService = testInjector.resolve( + ProjectServiceLib.ProjectService + ); + return { projectService, copies, extracted: () => extracted }; + }; + + it("copies the whole App_Resources when it is missing", async () => { + const { projectService, copies } = setup([]); + await projectService.ensureAppResourcesExist(projectDir, "Windows"); + assert.deepEqual(copies, [ + { from: path.join(templateAppResources, "*"), to: appResources }, + ]); + }); + + it("adds a missing platform folder from the default template", async () => { + const { projectService, copies } = setup([ + appResources, + path.join(templateAppResources, "Windows"), + ]); + await projectService.ensureAppResourcesExist(projectDir, "Windows"); + assert.deepEqual(copies, [ + { from: path.join(templateAppResources, "Windows"), to: appResources }, + ]); + }); + + it("leaves an existing platform folder alone", async () => { + const { projectService, copies, extracted } = setup([ + appResources, + path.join(appResources, "Windows"), + ]); + await projectService.ensureAppResourcesExist(projectDir, "Windows"); + assert.deepEqual(copies, []); + assert.equal(extracted(), 0); + }); + + it("copies nothing when the template has no folder for the platform", async () => { + const { projectService, copies } = setup([appResources]); + await projectService.ensureAppResourcesExist(projectDir, "Windows"); + assert.deepEqual(copies, []); + }); + + it("does not look at platform folders without a platform", async () => { + const { projectService, copies, extracted } = setup([appResources]); + await projectService.ensureAppResourcesExist(projectDir); + assert.deepEqual(copies, []); + assert.equal(extracted(), 0); + }); + }); });