diff --git a/.changeset/sixty-wombats-collect.md b/.changeset/sixty-wombats-collect.md new file mode 100644 index 00000000..ebe0f606 --- /dev/null +++ b/.changeset/sixty-wombats-collect.md @@ -0,0 +1,5 @@ +--- +"create-webstone-app": minor +--- + +add functionality to turn existing sveltekit app to a webstone app diff --git a/package.json b/package.json index 6300e4d0..67ddd7b6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "devDependencies": { "@changesets/cli": "^2.23.2", - "@playwright/test": "^1.23.4", + "@playwright/test": "^1.25.0", "@types/fs-extra": "^9.0.13", "@types/node": "16.11.7", "@types/sinon": "^10.0.12", diff --git a/packages/create-webstone-app/package.json b/packages/create-webstone-app/package.json index d97a4b04..1ab1f183 100644 --- a/packages/create-webstone-app/package.json +++ b/packages/create-webstone-app/package.json @@ -30,10 +30,11 @@ }, "dependencies": { "chalk": "5.0.1", - "create-svelte": "2.0.0-next.149", + "create-svelte": "2.0.0-next.159", "enquirer": "2.3.6", "execa": "6.1.0", "fs-extra": "10.1.0", - "listr2": "4.0.5" + "listr2": "4.0.5", + "tempy": "^3.0.0" } } diff --git a/packages/create-webstone-app/src/helpers.ts b/packages/create-webstone-app/src/helpers.ts index 7a35cf89..3993a17b 100644 --- a/packages/create-webstone-app/src/helpers.ts +++ b/packages/create-webstone-app/src/helpers.ts @@ -1,10 +1,16 @@ import chalk from "chalk"; +import fs from "fs-extra"; +import path from "path"; import { ListrTaskWrapper, ListrRenderer } from "listr2/dist/index"; +const minimalVersion = 405; + export interface Ctx { appDir: string; - webAppDir?: string; + isSveltekit: boolean; + tempDir: string; + webAppDir: string; } export type WebstoneTask = ListrTaskWrapper; @@ -41,3 +47,26 @@ Next steps: - ${chalk.bold(chalk.cyan("pnpm ws dev"))} `); }; + +export const isSveltekit = (appDir: string) => { + if (fs.existsSync(path.join(appDir, "package.json"))) { + const packageJson = fs.readJsonSync(path.join(appDir, "package.json")); + if (packageJson.devDependencies["@sveltejs/kit"]) { + return true; + } + } + return false; +}; + +export const checkCorrectSveltekitVersion = (version: string) => { + const versionRegex = /^1\.0\.0-next\.(\d{3})$/; + const error = Error( + `Please upgrade to a Sveltekit Version greater than 1.0.0-next.${minimalVersion}` + ); + if (version === "next") return true; + const matches = version.match(versionRegex); + if (!matches) throw error; + const [, kitVersion] = matches; + if (parseInt(kitVersion) < minimalVersion) throw error; + return true; +}; diff --git a/packages/create-webstone-app/src/tasks/1-create-app-directory/index.ts b/packages/create-webstone-app/src/tasks/1-create-app-directory/index.ts index 56345154..80eadddb 100644 --- a/packages/create-webstone-app/src/tasks/1-create-app-directory/index.ts +++ b/packages/create-webstone-app/src/tasks/1-create-app-directory/index.ts @@ -1,7 +1,12 @@ import fs from "fs-extra"; import { ListrTask } from "listr2/dist/index"; -import { Ctx, WebstoneTask } from "../../helpers"; +import { + checkCorrectSveltekitVersion, + Ctx, + isSveltekit, + WebstoneTask, +} from "../../helpers.js"; const determineAppDirName = async (ctx: Ctx, task: WebstoneTask) => { const appName = process.argv[2]; @@ -16,6 +21,24 @@ export const createAppDir = async (ctx: Ctx, task: WebstoneTask) => { const appDir = ctx.appDir; if (fs.existsSync(appDir)) { + if (isSveltekit(appDir)) { + ctx.isSveltekit = true; + const isSveltekitResponse = await task.prompt({ + type: "Confirm", + message: + "This directory already contains a Sveltekit project. Do you want to turn the existing project to a webstone app?", + initial: false, + }); + if (!isSveltekitResponse) { + throw new Error("Exiting, app is already a Sveltekit project"); + } + checkCorrectSveltekitVersion( + fs.readJsonSync(`${appDir}/package.json`).devDependencies[ + "@sveltejs/kit" + ] + ); + return appDir; + } if (fs.readdirSync(appDir).length > 0) { const response = await task.prompt({ type: "confirm", diff --git a/packages/create-webstone-app/src/tasks/2-prepare-project-structure/index.ts b/packages/create-webstone-app/src/tasks/2-prepare-project-structure/index.ts index b4c5fc3b..5c3906de 100644 --- a/packages/create-webstone-app/src/tasks/2-prepare-project-structure/index.ts +++ b/packages/create-webstone-app/src/tasks/2-prepare-project-structure/index.ts @@ -2,12 +2,31 @@ import fs from "fs-extra"; import path, { dirname } from "path"; import { ListrTask } from "listr2/dist/index"; import { fileURLToPath } from "url"; +import { temporaryDirectory } from "tempy"; + +const ignorePaths = [ + "node_modules", + "build", + ".svelte-kit", + "package", + "package-lock.json", + "yarn.lock", +]; import { Ctx } from "../../helpers"; const __dirname = dirname(fileURLToPath(import.meta.url)); export const copyTemplate = (ctx: Ctx) => { + ctx.webAppDir = `${ctx.appDir}/services/web`; + if (ctx.isSveltekit) { + ctx.tempDir = temporaryDirectory({ prefix: "webstone" }); + ignorePaths.forEach((pathString: string) => { + fs.existsSync(path.join(ctx.appDir, pathString)) && + fs.rmdirSync(path.join(ctx.appDir, pathString)); + }); + fs.moveSync(ctx.appDir, ctx.tempDir, { overwrite: true }); + } const templateDir = path.join(__dirname, "../../..", "template"); fs.copySync(templateDir, ctx.appDir); }; diff --git a/packages/create-webstone-app/src/tasks/3-install-app-dependencies/index.ts b/packages/create-webstone-app/src/tasks/3-install-app-dependencies/index.ts index c36d0e54..f532b08a 100644 --- a/packages/create-webstone-app/src/tasks/3-install-app-dependencies/index.ts +++ b/packages/create-webstone-app/src/tasks/3-install-app-dependencies/index.ts @@ -8,9 +8,13 @@ import { Ctx } from "../../helpers"; const initWebApp = async (ctx: Ctx) => { const webAppDir = `${ctx.appDir}/services/web`; - ctx.webAppDir = webAppDir; console.log(`Installing web app in ${webAppDir}...`); - + if (ctx.isSveltekit) { + fs.emptyDirSync(ctx.webAppDir); + fs.moveSync(ctx.tempDir, ctx.webAppDir, { overwrite: true }); + fs.removeSync(ctx.tempDir); + return; + } try { fs.removeSync(`${webAppDir}/.keep`); diff --git a/packages/create-webstone-app/tests/helpers/create-app-dir.ts b/packages/create-webstone-app/tests/helpers/create-app-dir.ts index ee975fde..cc246cc0 100644 --- a/packages/create-webstone-app/tests/helpers/create-app-dir.ts +++ b/packages/create-webstone-app/tests/helpers/create-app-dir.ts @@ -18,7 +18,7 @@ test("app dir does not exist, no app dir provided", async () => { output: "", }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: ".", }; @@ -51,7 +51,7 @@ test("app dir does not exist, app dir with space", async () => { output: "", }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: "test-app", }; const fakeFsExistsSync = sinon.fake.returns(false); @@ -72,7 +72,7 @@ test("app dir does not exist", async () => { output: "", }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: "test-app", }; @@ -94,11 +94,13 @@ test("app dir exists and is empty", async () => { output: "", }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: "test-app", }; - const fakeFsExistsSync = sinon.fake.returns(true); + const fakeFsExistsSync = sinon.stub(); + fakeFsExistsSync.onFirstCall().returns(true); + fakeFsExistsSync.onSecondCall().returns(false); sinon.replace(fs, "existsSync", fakeFsExistsSync); const fakeFsReaddirSync = sinon.fake.returns([]); @@ -121,10 +123,12 @@ test("app dir exists and is not empty, overwrite it", async () => { prompt: fakeListrPrompt, }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: "test-app", }; - const fakeFsExistsSync = sinon.fake.returns(true); + const fakeFsExistsSync = sinon.stub(); + fakeFsExistsSync.onFirstCall().returns(true); + fakeFsExistsSync.onSecondCall().returns(false); sinon.replace(fs, "existsSync", fakeFsExistsSync); const fakeFsReaddirSync = sinon.fake.returns([ @@ -153,10 +157,12 @@ test("app dir exists and is not empty, do not overwrite it", async () => { prompt: fakeListrPrompt, }; - const fakeContext: Ctx = { + const fakeContext: Partial = { appDir: "test-app", }; - const fakeFsExistsSync = sinon.fake.returns(true); + const fakeFsExistsSync = sinon.stub(); + fakeFsExistsSync.onFirstCall().returns(true); + fakeFsExistsSync.onSecondCall().returns(false); sinon.replace(fs, "existsSync", fakeFsExistsSync); const fakeFsReaddirSync = sinon.fake.returns([ diff --git a/packages/create-webstone-app/tests/helpers/helpers.ts b/packages/create-webstone-app/tests/helpers/helpers.ts new file mode 100644 index 00000000..5228a074 --- /dev/null +++ b/packages/create-webstone-app/tests/helpers/helpers.ts @@ -0,0 +1,67 @@ +import sinon from "sinon"; +import { test } from "uvu"; +import * as assert from "uvu/assert"; +import fs from "fs-extra"; +import { isSveltekit, checkCorrectSveltekitVersion } from "../../src/helpers"; + +test.before.each(() => { + sinon.replace(console, "log", sinon.fake()); +}); + +test.after.each(() => { + sinon.restore(); +}); + +test("successfully recognize Sveltekit app", async () => { + const fakePackageJson = { + devDependencies: { + "@sveltejs/kit": "1.0.0-next.999", + }, + }; + const fakeReadJsonSync = sinon.fake.returns(fakePackageJson); + const fakeExists = sinon.fake.returns(true); + + sinon.replace(fs, "readJsonSync", fakeReadJsonSync); + sinon.replace(fs, "existsSync", fakeExists); + + assert.equal(isSveltekit("testapp"), true); +}); + +test("recognize, that project is not a sveltekit app", async () => { + const fakePackageJson = { + devDependencies: { + react: "16.8.6", + }, + }; + + const fakeExists = sinon.fake.returns(true); + const fakeReadJsonSync = sinon.fake.returns(fakePackageJson); + sinon.replace(fs, "readJsonSync", fakeReadJsonSync); + sinon.replace(fs, "existsSync", fakeExists); + + assert.equal(isSveltekit("testapp"), false); +}); + +test("no package.json provided", async () => { + const fakeExists = sinon.fake.returns(false); + sinon.replace(fs, "existsSync", fakeExists); + + assert.equal(isSveltekit("testapp"), false); +}); + +test("wrong version of sveltekit", async () => { + try { + checkCorrectSveltekitVersion("1.0.0.next.0"); + } catch (e) { + assert.match( + e.message, + /Please upgrade to a Sveltekit Version greater than 1.0.0-next.\d{3}/ + ); + } +}); + +test("correct version of sveltekit", async () => { + assert.ok(checkCorrectSveltekitVersion("1.0.0-next.999")); +}); + +test.run(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf485921..31422a5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ importers: .: specifiers: '@changesets/cli': ^2.23.2 - '@playwright/test': ^1.23.4 + '@playwright/test': ^1.25.0 '@types/fs-extra': ^9.0.13 '@types/node': 16.11.7 '@types/sinon': ^10.0.12 @@ -25,7 +25,7 @@ importers: uvu: ^0.5.6 devDependencies: '@changesets/cli': 2.23.2 - '@playwright/test': 1.23.4 + '@playwright/test': 1.25.2 '@types/fs-extra': 9.0.13 '@types/node': 16.11.7 '@types/sinon': 10.0.12 @@ -63,19 +63,21 @@ importers: specifiers: '@types/node': 16.11.7 chalk: 5.0.1 - create-svelte: 2.0.0-next.149 + create-svelte: 2.0.0-next.159 enquirer: 2.3.6 execa: 6.1.0 fs-extra: 10.1.0 listr2: 4.0.5 + tempy: ^3.0.0 typescript: ^4.7.4 dependencies: chalk: 5.0.1 - create-svelte: 2.0.0-next.149 + create-svelte: 2.0.0-next.159 enquirer: 2.3.6 execa: 6.1.0 fs-extra: 10.1.0 listr2: 4.0.5_enquirer@2.3.6 + tempy: 3.0.0 devDependencies: '@types/node': 16.11.7 typescript: 4.7.4 @@ -483,13 +485,13 @@ packages: fastq: 1.13.0 dev: true - /@playwright/test/1.23.4: - resolution: {integrity: sha512-iIsoMJDS/lyuhw82FtcV/B3PXikgVD3hNe5hyvOpRM0uRr1OIpN3LgPeRbBjhzBWmyf6RgRg5fqK5sVcpA03yA==} + /@playwright/test/1.25.2: + resolution: {integrity: sha512-6qPznIR4Fw02OMbqXUPMG6bFFg1hDVNEdihKy0t9K0dmRbus1DyP5Q5XFQhGwEHQkLG5hrSfBuu9CW/foqhQHQ==} engines: {node: '>=14'} hasBin: true dependencies: '@types/node': 16.11.7 - playwright-core: 1.23.4 + playwright-core: 1.25.2 dev: true /@sinonjs/commons/1.8.3: @@ -1216,8 +1218,8 @@ packages: dev: true optional: true - /create-svelte/2.0.0-next.149: - resolution: {integrity: sha512-Oi2rNryn02ZkmqcihtcQMbqWqb1L3FODrZNYZgyaGLm+4lrJL1NVI7sef43N8+qc1ArMXM61qdbEz9SL3Rn8Ag==} + /create-svelte/2.0.0-next.159: + resolution: {integrity: sha512-zM6L7k9jS7h0LV21qIl3k1ovywzUok78aNQ7VYmdZvBGS4kde/V/bsHX1kfCJ3FghEALyn4rJmhSqVaH2MMFWQ==} hasBin: true dependencies: kleur: 4.1.5 @@ -1240,6 +1242,13 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /crypto-random-string/4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + dependencies: + type-fest: 1.4.0 + dev: false + /csv-generate/3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} dev: true @@ -3297,8 +3306,8 @@ packages: find-up: 4.1.0 dev: true - /playwright-core/1.23.4: - resolution: {integrity: sha512-h5V2yw7d8xIwotjyNrkLF13nV9RiiZLHdXeHo+nVJIYGVlZ8U2qV0pMxNJKNTvfQVT0N8/A4CW6/4EW2cOcTiA==} + /playwright-core/1.25.2: + resolution: {integrity: sha512-0yTbUE9lIddkEpLHL3u8PoCL+pWiZtj5A/j3U7YoNjcmKKDGBnCrgHJMzwd2J5vy6l28q4ki3JIuz7McLHhl1A==} engines: {node: '>=14'} hasBin: true dev: true @@ -3855,6 +3864,21 @@ packages: engines: {node: '>= 0.4'} dev: true + /temp-dir/2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: false + + /tempy/3.0.0: + resolution: {integrity: sha512-B2I9X7+o2wOaW4r/CWMkpOO9mdiTRCxXNgob6iGvPmfPWgH/KyUD6Uy5crtWBxIBe3YrNZKR2lSzv1JJKWD4vA==} + engines: {node: '>=14.16'} + dependencies: + is-stream: 3.0.0 + temp-dir: 2.0.0 + type-fest: 2.18.0 + unique-string: 3.0.0 + dev: false + /term-size/2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -4018,6 +4042,16 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: false + + /type-fest/2.18.0: + resolution: {integrity: sha512-pRS+/yrW5TjPPHNOvxhbNZexr2bS63WjrMU8a+VzEBhUi9Tz1pZeD+vQz3ut0svZ46P+SRqMEPnJmk2XnvNzTw==} + engines: {node: '>=12.20'} + dev: false + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -4037,6 +4071,13 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true + /unique-string/3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + dependencies: + crypto-random-string: 4.0.0 + dev: false + /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'}