├── templates ├── vue │ ├── README.md │ ├── .vscode │ │ └── extensions.json │ ├── src │ │ ├── main.js │ │ ├── components │ │ │ └── HelloWorld.vue │ │ ├── assets │ │ │ └── vue.svg │ │ ├── App.vue │ │ ├── index.html │ │ └── style.css │ └── build.js ├── react-ts │ ├── README.md │ ├── src │ │ ├── App.tsx │ │ ├── index.tsx │ │ ├── index.html │ │ └── index.css │ ├── test │ │ └── index.ts │ ├── tsconfig.json │ └── build.js ├── react │ ├── README.md │ ├── test │ │ └── index.js │ ├── src │ │ ├── index.jsx │ │ ├── index.html │ │ └── index.css │ └── build.js ├── svelte │ ├── README.md │ ├── .vscode │ │ └── extensions.json │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.js │ │ ├── lib │ │ │ └── Counter.svelte │ │ ├── index.html │ │ ├── App.svelte │ │ ├── app.css │ │ ├── svelte.svg │ │ └── assets │ │ │ └── svelte.svg │ └── build.js ├── tonic │ ├── README.md │ ├── test │ │ └── index.js │ ├── src │ │ ├── index.js │ │ ├── index.html │ │ └── index.css │ └── build.js └── vanilla │ ├── README.md │ ├── src │ ├── index.js │ ├── index.css │ └── index.html │ ├── test │ └── index.js │ └── build.js ├── .npmrc ├── README.md ├── common └── src │ └── icon.png ├── .github ├── dependabot.yml └── workflows │ └── tests.yml ├── .gitignore ├── package.json ├── test.js └── index.js /templates/vue/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/react-ts/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/react/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/svelte/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/tonic/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/vanilla/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > This tool has been deprecated. 3 | -------------------------------------------------------------------------------- /templates/svelte/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /common/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketsupply/create-socket-app/HEAD/common/src/icon.png -------------------------------------------------------------------------------- /templates/svelte/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /templates/vue/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /templates/vue/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /templates/svelte/src/main.js: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app') 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /templates/react-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import os from 'socket:os'; 2 | import React from 'react'; 3 | 4 | const App = () => { 5 | return

Hello, {os.platform()}!

; 6 | }; 7 | export default App; 8 | -------------------------------------------------------------------------------- /templates/svelte/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /templates/react/test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'socket:test' 2 | import os from 'socket:os' 3 | 4 | test('test', async t => { 5 | const label1 = document.querySelector('h1').textContent 6 | t.equal(label1, `Hello, ${os.platform()}`, 'label on start is correct') 7 | }) 8 | -------------------------------------------------------------------------------- /templates/tonic/test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'socket:test' 2 | import os from 'socket:os' 3 | 4 | test('test', async t => { 5 | const label1 = document.querySelector('h1').textContent 6 | t.equal(label1, `Hello, ${os.platform()}`, 'label on start is correct') 7 | }) 8 | -------------------------------------------------------------------------------- /templates/react-ts/test/index.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'socket:test'; 2 | import os from 'socket:os'; 3 | 4 | test('test', async (t: any) => { 5 | const label1 = document.querySelector('h1')?.textContent; 6 | t.equal(label1, `Hello, ${os.platform()}`, 'label on start is correct'); 7 | }); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | *.dat 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Editor directories and files 12 | .vscode/* 13 | !.vscode/extensions.json 14 | .idea 15 | .DS_Store 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | 22 | # Ignore all files in the node_modules folder 23 | node_modules/ 24 | package-lock.json 25 | coverage 26 | -------------------------------------------------------------------------------- /templates/vanilla/src/index.js: -------------------------------------------------------------------------------- 1 | import process from 'socket:process' 2 | import os from 'socket:os' 3 | 4 | if (process.env.DEBUG) { 5 | console.log('started in debug mode') 6 | } 7 | 8 | window.addEventListener('DOMContentLoaded', () => { 9 | const platform = os.platform() 10 | 11 | setTimeout(() => { 12 | const h1 = document.querySelector('h1') 13 | h1.textContent = `Hello, ${platform}!` 14 | }, 2048) 15 | }) 16 | -------------------------------------------------------------------------------- /templates/vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /templates/react/src/index.jsx: -------------------------------------------------------------------------------- 1 | import process from 'socket:process' 2 | import os from 'socket:os' 3 | 4 | import { createRoot } from 'react-dom/client' 5 | import React from 'react' 6 | 7 | if (process.env.DEBUG) { 8 | console.log('started in debug mode') 9 | } 10 | 11 | function AppContainer () { 12 | return

Hello, {os.platform()}!

13 | } 14 | 15 | const root = createRoot(document.getElementById('root')) 16 | root.render() 17 | -------------------------------------------------------------------------------- /templates/react-ts/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import process from 'socket:process'; 4 | 5 | if (process.env.DEBUG) { 6 | console.log('started in debug mode'); 7 | } 8 | 9 | // components 10 | import App from './App'; 11 | 12 | const root = createRoot(document.getElementById('root') as HTMLElement); 13 | 14 | root.render( 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /templates/tonic/src/index.js: -------------------------------------------------------------------------------- 1 | import Tonic from '@socketsupply/tonic' 2 | 3 | import process from 'socket:process' 4 | import os from 'socket:os' 5 | 6 | if (process.env.DEBUG) { 7 | console.log('started in debug mode') 8 | } 9 | 10 | class AppContainer extends Tonic { 11 | render () { 12 | const platform = os.platform() 13 | 14 | return this.html` 15 |

Hello, ${platform}!

16 | ` 17 | } 18 | } 19 | 20 | Tonic.add(AppContainer, 'app-container') 21 | -------------------------------------------------------------------------------- /templates/vue/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/vanilla/test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'socket:test' 2 | import os from 'socket:os' 3 | 4 | test('test', async t => { 5 | const label1 = document.querySelector('h1').textContent 6 | t.equal(label1, 'Hello, World', 'label on start is correct') 7 | 8 | // sleep 3 seconds 9 | await new Promise(resolve => setTimeout(resolve, 3000)) 10 | 11 | const label2 = document.querySelector('h1').textContent 12 | t.equal(label2, `Hello, ${os.platform()}!`, 'label after 3 seconds is correct') 13 | }) 14 | -------------------------------------------------------------------------------- /templates/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /templates/react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/*", 4 | "src/**/*", 5 | "tests/*", 6 | "tests/**/*" 7 | ], 8 | "compilerOptions": { 9 | "moduleResolution": "node", 10 | "types": ["@socketsupply/socket"], 11 | "target": "es2022", 12 | "module": "es2022", 13 | "lib": ["es2022", "es6", "dom"], 14 | 15 | "removeComments": false, 16 | 17 | "checkJs": true, 18 | "allowJs": true, 19 | "noEmit": true, 20 | "allowSyntheticDefaultImports": true, 21 | "jsx": "react", 22 | 23 | "strict": true, 24 | 25 | "declaration": true, 26 | "declarationMap": true, 27 | 28 | "baseUrl": "." 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /templates/react/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | Hello, World 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/vue/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | Vite + Vue 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /templates/svelte/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | Vite + Svelte 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /templates/tonic/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | Hello, World 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /templates/react-ts/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 16 | 17 | Hello, World 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/vanilla/src/index.css: -------------------------------------------------------------------------------- 1 | /* Minimal Reset */ 2 | html { 3 | box-sizing: border-box; 4 | font-size: 16px; 5 | font: -apple-system-body; 6 | } 7 | 8 | *, *:before, *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, h1, h2, h3, h4, h5, h6, p, ol, ul { 13 | margin: 0; 14 | padding: 0; 15 | font-weight: normal; 16 | } 17 | 18 | ol, ul { 19 | list-style: none; 20 | } 21 | 22 | img { 23 | max-width: 100%; 24 | height: auto; 25 | } 26 | 27 | /* Placeholder Styles */ 28 | body { 29 | height: 100vh; 30 | } 31 | 32 | .centered { 33 | position: absolute; 34 | top: 50%; 35 | left: 50%; 36 | transform: translate(-50%, -50%); 37 | font: -apple-system-title0; 38 | font-size: 2.5em; 39 | text-align: center; 40 | } 41 | -------------------------------------------------------------------------------- /templates/vanilla/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | Hello, World 15 | 16 | 17 |
18 |

Hello, World

19 |

Socket Runtime

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/react/src/index.css: -------------------------------------------------------------------------------- 1 | /* Minimal Reset */ 2 | html { 3 | box-sizing: border-box; 4 | font-size: 16px; 5 | } 6 | 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body, h1, h2, h3, h4, h5, h6, p, ol, ul { 12 | margin: 0; 13 | padding: 0; 14 | font-weight: normal; 15 | } 16 | 17 | ol, ul { 18 | list-style: none; 19 | } 20 | 21 | img { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | /* Placeholder Styles */ 27 | body { 28 | height: 100vh; 29 | font: -apple-system-body; 30 | background-color: var(--tonic-window); 31 | color: var(--tonic-primary); 32 | } 33 | 34 | h1 { 35 | position: absolute; 36 | top: 50%; 37 | left: 50%; 38 | transform: translate(-50%, -50%); 39 | font: -apple-system-title0; 40 | font-size: 2.5em; 41 | } 42 | -------------------------------------------------------------------------------- /templates/react-ts/src/index.css: -------------------------------------------------------------------------------- 1 | /* Minimal Reset */ 2 | html { 3 | box-sizing: border-box; 4 | font-size: 16px; 5 | } 6 | 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body, h1, h2, h3, h4, h5, h6, p, ol, ul { 12 | margin: 0; 13 | padding: 0; 14 | font-weight: normal; 15 | } 16 | 17 | ol, ul { 18 | list-style: none; 19 | } 20 | 21 | img { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | /* Placeholder Styles */ 27 | body { 28 | height: 100vh; 29 | font: -apple-system-body; 30 | background-color: var(--tonic-window); 31 | color: var(--tonic-primary); 32 | } 33 | 34 | h1 { 35 | position: absolute; 36 | top: 50%; 37 | left: 50%; 38 | transform: translate(-50%, -50%); 39 | font: -apple-system-title0; 40 | font-size: 2.5em; 41 | } 42 | -------------------------------------------------------------------------------- /templates/svelte/src/App.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 |
13 |

Vite + Svelte (on {toProperCase(os.platform())})

14 | 15 |
16 | 17 |
18 |
19 | 20 | 33 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | NO_ANDROID: 1 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | timeout-minutes: 10 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | node: ['lts/*'] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install dependencies (Linux) 24 | if: runner.os == 'Linux' 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install -y git libwebkit2gtk-4.1-dev build-essential libc++abi-14-dev libc++-14-dev pkg-config clang-14 28 | 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | 34 | - run: npm i @socketsupply/socket -g 35 | - run: ssc --version 36 | - run: npm i 37 | - run: npm test 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-socket-app", 3 | "version": "1.1.5", 4 | "description": "Create Socket App", 5 | "bin": { 6 | "create-socket-app": "./index.js" 7 | }, 8 | "engines": { 9 | "node": ">=16" 10 | }, 11 | "scripts": { 12 | "prepublishOnly": "git push --follow-tags", 13 | "test": "run-s test:*", 14 | "test:standard": "standard", 15 | "test:node-test": "c8 node --test-reporter spec --test ./test.js" 16 | }, 17 | "type": "module", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/socketsupply/create-socket-app.git" 21 | }, 22 | "author": "", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/socketsupply/create-socket-app/issues" 26 | }, 27 | "homepage": "https://github.com/socketsupply/create-socket-app#readme", 28 | "devDependencies": { 29 | "c8": "^8.0.1", 30 | "desm": "^1.3.0", 31 | "npm-run-all2": "^6.0.6", 32 | "p-temporary-directory": "^2.0.0", 33 | "standard": "^17.0.0" 34 | }, 35 | "c8": { 36 | "reporter": [ 37 | "lcov", 38 | "text" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /templates/svelte/src/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | .card { 41 | padding: 2em; 42 | } 43 | 44 | #app { 45 | max-width: 1280px; 46 | margin: 0 auto; 47 | padding: 2rem; 48 | text-align: center; 49 | } 50 | 51 | button { 52 | border-radius: 8px; 53 | border: 1px solid transparent; 54 | padding: 0.6em 1.2em; 55 | font-size: 1em; 56 | font-weight: 500; 57 | font-family: inherit; 58 | background-color: #1a1a1a; 59 | cursor: pointer; 60 | transition: border-color 0.25s; 61 | } 62 | button:hover { 63 | border-color: #646cff; 64 | } 65 | button:focus, 66 | button:focus-visible { 67 | outline: 4px auto -webkit-focus-ring-color; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'node:assert' 3 | import tmp from 'p-temporary-directory' 4 | import path from 'node:path' 5 | import cp from 'node:child_process' 6 | import os from 'node:os' 7 | import desm from 'desm' 8 | 9 | const __dirname = desm(import.meta.url) 10 | 11 | const cliPath = path.join(__dirname, 'index.js') 12 | 13 | test('test all the templates', async (t) => { 14 | let dir, cleanup 15 | t.beforeEach(async () => { 16 | [dir, cleanup] = await tmp() 17 | }) 18 | 19 | t.afterEach(async () => { 20 | await cleanup() 21 | }) 22 | 23 | const templates = ['tonic', 'react', 'react-ts', 'vanilla', 'vue', 'svelte'] 24 | 25 | for (const template of templates) { 26 | await t.test(`${template} template`, async (t) => { 27 | await assert.doesNotReject(async () => { 28 | return new Promise((resolve, reject) => { 29 | const child = os.platform() === 'win32' 30 | ? cp.spawn('node', [cliPath, template], { cwd: dir }) 31 | : cp.spawn(cliPath, [template], { cwd: dir }) 32 | 33 | child.stdout.on('data', (data) => { 34 | console.log(`stdout: ${data}`) 35 | }) 36 | 37 | child.stderr.on('data', (data) => { 38 | console.error(`stderr: ${data}`) 39 | }) 40 | 41 | child.on('error', (error) => { 42 | reject(error) 43 | }) 44 | 45 | child.on('close', (code) => { 46 | if (code !== 0) { 47 | reject(new Error(`child process exited with code ${code}`)) 48 | } else { 49 | resolve() 50 | } 51 | }) 52 | }) 53 | }) 54 | }) 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /templates/vue/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | a { 28 | font-weight: 500; 29 | color: #646cff; 30 | text-decoration: inherit; 31 | } 32 | a:hover { 33 | color: #535bf2; 34 | } 35 | 36 | body { 37 | margin: 0; 38 | display: flex; 39 | place-items: center; 40 | min-width: 320px; 41 | min-height: 100vh; 42 | } 43 | 44 | h1 { 45 | font-size: 3.2em; 46 | line-height: 1.1; 47 | } 48 | 49 | button { 50 | border-radius: 8px; 51 | border: 1px solid transparent; 52 | padding: 0.6em 1.2em; 53 | font-size: 1em; 54 | font-weight: 500; 55 | font-family: inherit; 56 | background-color: #1a1a1a; 57 | cursor: pointer; 58 | transition: border-color 0.25s; 59 | } 60 | button:hover { 61 | border-color: #646cff; 62 | } 63 | button:focus, 64 | button:focus-visible { 65 | outline: 4px auto -webkit-focus-ring-color; 66 | } 67 | 68 | .card { 69 | padding: 2em; 70 | } 71 | 72 | #app { 73 | max-width: 1280px; 74 | margin: 0 auto; 75 | padding: 2rem; 76 | text-align: center; 77 | } 78 | 79 | @media (prefers-color-scheme: light) { 80 | :root { 81 | color: #213547; 82 | background-color: #ffffff; 83 | } 84 | a:hover { 85 | color: #747bff; 86 | } 87 | button { 88 | background-color: #f9f9f9; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /templates/vue/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import path from 'node:path' 6 | import fs from 'node:fs' 7 | import { build } from 'vite' 8 | import vue from '@vitejs/plugin-vue' 9 | 10 | async function main () { 11 | const prod = process.argv.find(s => s.includes('--prod')) 12 | 13 | const watch = process.argv.find(s => s.includes('--watch=')) 14 | 15 | // 16 | // The second argument to this program will be the target-OS specifc 17 | // directory for where to copy your build artifacts 18 | // 19 | const target = path.resolve(process.env.PREFIX) 20 | 21 | // 22 | // If the watch command is specified, let esbuild start its server 23 | // 24 | // TODO: Implement watch mode 25 | 26 | // 27 | // 28 | // 29 | if (!watch) { 30 | await build({ 31 | root: path.resolve('./src'), 32 | mode: prod ? 'production' : 'development', 33 | base: './', 34 | plugins: [vue()], 35 | build: { 36 | outDir: target, 37 | emptyOutDir: false, 38 | sourcemap: !prod, 39 | minify: prod ? 'esbuild' : false, 40 | rollupOptions: { 41 | external: [/socket:.*/] 42 | } 43 | // modulePreload: { 44 | // polyfill: false 45 | // }, 46 | } 47 | }) 48 | } 49 | // TODO: Implement test mode 50 | // if (process.argv.find(s => s.includes('--test'))) { 51 | // ... 52 | // } 53 | 54 | // 55 | // Not writing a package json to your project could be a security risk 56 | // 57 | await fs.promises.writeFile(path.join(target, 'package.json'), '{ "private": true }') 58 | 59 | if (!target) { 60 | console.log('Did not receive the build target path as an argument!') 61 | process.exit(1) 62 | } 63 | } 64 | 65 | main() 66 | -------------------------------------------------------------------------------- /templates/svelte/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import path from 'node:path' 6 | import fs from 'node:fs' 7 | import { build } from 'vite' 8 | import { svelte } from '@sveltejs/vite-plugin-svelte' 9 | 10 | async function main () { 11 | const prod = process.argv.find(s => s.includes('--prod')) 12 | 13 | const watch = process.argv.find(s => s.includes('--watch=')) 14 | 15 | // 16 | // The second argument to this program will be the target-OS specifc 17 | // directory for where to copy your build artifacts 18 | // 19 | const target = path.resolve(process.env.PREFIX) 20 | 21 | // 22 | // If the watch command is specified, let esbuild start its server 23 | // 24 | // TODO: Implement watch mode 25 | 26 | // 27 | // 28 | // 29 | if (!watch) { 30 | await build({ 31 | root: path.resolve('./src'), 32 | mode: prod ? 'production' : 'development', 33 | base: './', 34 | plugins: [svelte()], 35 | build: { 36 | outDir: target, 37 | emptyOutDir: false, 38 | sourcemap: !prod, 39 | minify: prod ? 'esbuild' : false, 40 | rollupOptions: { 41 | external: [/socket:.*/] 42 | } 43 | // modulePreload: { 44 | // polyfill: false 45 | // }, 46 | } 47 | }) 48 | } 49 | // TODO: Implement test mode 50 | // if (process.argv.find(s => s.includes('--test'))) { 51 | // ... 52 | // } 53 | 54 | // 55 | // Not writing a package json to your project could be a security risk 56 | // 57 | await fs.promises.writeFile(path.join(target, 'package.json'), '{ "type": "module", private": true }') 58 | 59 | if (!target) { 60 | console.log('Did not receive the build target path as an argument!') 61 | process.exit(1) 62 | } 63 | } 64 | 65 | main() 66 | -------------------------------------------------------------------------------- /templates/svelte/src/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/svelte/src/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/tonic/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import fs from 'node:fs' 6 | import path from 'node:path' 7 | 8 | import esbuild from 'esbuild' 9 | 10 | const cp = async (a, b) => fs.promises.cp( 11 | path.resolve(a), 12 | path.join(b, path.basename(a)), 13 | { recursive: true, force: true } 14 | ) 15 | 16 | async function main () { 17 | const prod = process.argv.find(s => s.includes('--prod')) 18 | 19 | const params = { 20 | entryPoints: ['src/index.js'], 21 | format: 'esm', 22 | bundle: true, 23 | minify: !!prod, 24 | sourcemap: !prod, 25 | external: ['socket:*'] 26 | } 27 | 28 | const watch = process.argv.find(s => s.includes('--watch=')) 29 | 30 | // 31 | // The second argument to this program will be the target-OS specifc 32 | // directory for where to copy your build artifacts 33 | // 34 | const target = path.resolve(process.env.PREFIX) 35 | 36 | // 37 | // If the watch command is specified, let esbuild start its server 38 | // 39 | if (watch) { 40 | esbuild.serve({ servedir: path.resolve(watch.split('=')[1]) }, params) 41 | } 42 | 43 | // 44 | // 45 | // 46 | if (!watch) { 47 | await esbuild.build({ 48 | ...params, 49 | outdir: target 50 | }) 51 | } 52 | if (process.argv.find(s => s.includes('--test'))) { 53 | await esbuild.build({ 54 | ...params, 55 | entryPoints: ['test/index.js'], 56 | outdir: path.join(target, 'test') 57 | }) 58 | } 59 | 60 | // 61 | // Not writing a package json to your project could be a security risk 62 | // 63 | await fs.promises.writeFile(path.join(target, 'package.json'), '{ "private": true }') 64 | 65 | if (!target) { 66 | console.log('Did not receive the build target path as an argument!') 67 | process.exit(1) 68 | } 69 | 70 | // 71 | // Copy some files into the new project 72 | // 73 | await Promise.all([ 74 | cp('src/index.html', target), 75 | cp('src/index.css', target), 76 | cp('src/icon.png', target) 77 | ]) 78 | } 79 | 80 | main() 81 | -------------------------------------------------------------------------------- /templates/vanilla/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import fs from 'node:fs' 6 | import path from 'node:path' 7 | 8 | import esbuild from 'esbuild' 9 | 10 | const cp = async (a, b) => fs.promises.cp( 11 | path.resolve(a), 12 | path.join(b, path.basename(a)), 13 | { recursive: true, force: true } 14 | ) 15 | 16 | async function main () { 17 | const prod = process.argv.find(s => s.includes('--prod')) 18 | 19 | const params = { 20 | entryPoints: ['src/index.js'], 21 | format: 'esm', 22 | bundle: true, 23 | minify: !!prod, 24 | sourcemap: !prod, 25 | external: ['socket:*'] 26 | } 27 | 28 | const watch = process.argv.find(s => s.includes('--watch=')) 29 | 30 | // 31 | // The second argument to this program will be the target-OS specifc 32 | // directory for where to copy your build artifacts 33 | // 34 | const target = path.resolve(process.env.PREFIX) 35 | 36 | // 37 | // If the watch command is specified, let esbuild start its server 38 | // 39 | if (watch) { 40 | esbuild.serve({ servedir: path.resolve(watch.split('=')[1]) }, params) 41 | } 42 | 43 | // 44 | // 45 | // 46 | if (!watch) { 47 | await esbuild.build({ 48 | ...params, 49 | outdir: target 50 | }) 51 | } 52 | if (process.argv.find(s => s.includes('--test'))) { 53 | await esbuild.build({ 54 | ...params, 55 | entryPoints: ['test/index.js'], 56 | outdir: path.join(target, 'test') 57 | }) 58 | } 59 | 60 | // 61 | // Not writing a package json to your project could be a security risk 62 | // 63 | await fs.promises.writeFile(path.join(target, 'package.json'), '{ "private": true }') 64 | 65 | if (!target) { 66 | console.log('Did not receive the build target path as an argument!') 67 | process.exit(1) 68 | } 69 | 70 | // 71 | // Copy some files into the new project 72 | // 73 | await Promise.all([ 74 | cp('src/index.html', target), 75 | cp('src/index.css', target), 76 | cp('src/icon.png', target) 77 | ]) 78 | } 79 | 80 | main() 81 | -------------------------------------------------------------------------------- /templates/react/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import fs from 'node:fs' 6 | import path from 'node:path' 7 | 8 | import esbuild from 'esbuild' 9 | 10 | const cp = async (a, b) => fs.promises.cp( 11 | path.resolve(a), 12 | path.join(b, path.basename(a)), 13 | { recursive: true, force: true } 14 | ) 15 | 16 | async function main () { 17 | const prod = process.argv.find(s => s.includes('--prod')) 18 | 19 | const params = { 20 | entryPoints: ['src/index.jsx'], 21 | format: 'esm', 22 | bundle: true, 23 | minify: !!prod, 24 | sourcemap: !prod, 25 | external: ['socket:*'] 26 | } 27 | 28 | const watch = process.argv.find(s => s.includes('--watch=')) 29 | 30 | // 31 | // The second argument to this program will be the target-OS specifc 32 | // directory for where to copy your build artifacts 33 | // 34 | const target = path.resolve(process.env.PREFIX) 35 | 36 | // 37 | // If the watch command is specified, let esbuild start its server 38 | // 39 | if (watch) { 40 | esbuild.serve({ servedir: path.resolve(watch.split('=')[1]) }, params) 41 | } 42 | 43 | // 44 | // 45 | // 46 | if (!watch) { 47 | await esbuild.build({ 48 | ...params, 49 | outfile: path.join(target, 'index.js') 50 | }) 51 | } 52 | if (process.argv.find(s => s.includes('--test'))) { 53 | await esbuild.build({ 54 | ...params, 55 | entryPoints: ['test/index.js'], 56 | outdir: path.join(target, 'test') 57 | }) 58 | } 59 | 60 | // 61 | // Not writing a package json to your project could be a security risk 62 | // 63 | await fs.promises.writeFile(path.join(target, 'package.json'), '{ "type": "module", "private": true }') 64 | 65 | if (!target) { 66 | console.log('Did not receive the build target path as an argument!') 67 | process.exit(1) 68 | } 69 | 70 | // 71 | // Copy some files into the new project 72 | // 73 | await Promise.all([ 74 | cp('src/index.html', target), 75 | cp('src/index.css', target), 76 | cp('src/icon.png', target) 77 | ]) 78 | } 79 | 80 | main() 81 | -------------------------------------------------------------------------------- /templates/react-ts/build.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example build script for Socket Runtime 3 | // When you run 'ssc build', this script (node build.js) will be run 4 | // 5 | import fs from 'node:fs' 6 | import path from 'node:path' 7 | 8 | import esbuild from 'esbuild' 9 | 10 | const cp = async (a, b) => 11 | fs.promises.cp(path.resolve(a), path.join(b, path.basename(a)), { 12 | recursive: true, 13 | force: true 14 | }) 15 | 16 | async function main () { 17 | const prod = process.argv.find((s) => s.includes('--prod')) 18 | 19 | const params = { 20 | entryPoints: ['src/index.tsx'], 21 | format: 'esm', 22 | bundle: true, 23 | minify: !!prod, 24 | sourcemap: !prod, 25 | external: ['socket:*'] 26 | } 27 | 28 | const watch = process.argv.find((s) => s.includes('--watch=')) 29 | 30 | // 31 | // The second argument to this program will be the target-OS specifc 32 | // directory for where to copy your build artifacts 33 | // 34 | const target = path.resolve(process.env.PREFIX) 35 | 36 | // 37 | // If the watch command is specified, let esbuild start its server 38 | // 39 | if (watch) { 40 | esbuild.serve({ servedir: path.resolve(watch.split('=')[1]) }, params) 41 | } 42 | 43 | // 44 | // 45 | // 46 | if (!watch) { 47 | await esbuild.build({ 48 | ...params, 49 | outfile: path.join(target, 'index.js') 50 | }) 51 | } 52 | if (process.argv.find((s) => s.includes('--test'))) { 53 | await esbuild.build({ 54 | ...params, 55 | entryPoints: ['test/index.ts'], 56 | outdir: path.join(target, 'test') 57 | }) 58 | } 59 | 60 | // 61 | // Not writing a package json to your project could be a security risk 62 | // 63 | await fs.promises.writeFile( 64 | path.join(target, 'package.json'), 65 | '{ "type": "module", "private": true }' 66 | ) 67 | 68 | if (!target) { 69 | console.log('Did not receive the build target path as an argument!') 70 | process.exit(1) 71 | } 72 | 73 | // 74 | // Copy some files into the new project 75 | // 76 | await Promise.all([ 77 | cp('src/index.html', target), 78 | cp('src/index.css', target), 79 | cp('src/icon.png', target) 80 | ]) 81 | } 82 | 83 | main() 84 | -------------------------------------------------------------------------------- /templates/tonic/src/index.css: -------------------------------------------------------------------------------- 1 | /* Minimal Reset */ 2 | html { 3 | box-sizing: border-box; 4 | font-size: 16px; 5 | } 6 | 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body, h1, h2, h3, h4, h5, h6, p, ol, ul { 12 | margin: 0; 13 | padding: 0; 14 | font-weight: normal; 15 | } 16 | 17 | ol, ul { 18 | list-style: none; 19 | } 20 | 21 | img { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | /* Tonic theme */ 27 | 28 | body { 29 | --tonic-body: 'Inter', sans-serif; 30 | --tonic-header: 'Inter Black', sans-serif; 31 | --tonic-subheader: 'Inter Medium', sans-serif; 32 | --tonic-monospace: 'FiraMono', monospace; 33 | } 34 | 35 | @media (prefers-color-scheme: light) { 36 | body, *[theme="light"] { 37 | --tonic-background: rgba(245, 245, 245, 1); 38 | --tonic-background-dark: rgba(238, 238, 238, 1); 39 | --tonic-window: rgba(255, 255, 255, 1); 40 | --tonic-accent: rgba(56, 185, 255, 1); 41 | --tonic-primary: rgba(54, 57, 61, 1); 42 | --tonic-secondary: rgba(160, 160, 160, 1); 43 | --tonic-light: rgba(153, 157, 160, 1); 44 | --tonic-medium: rgba(153, 157, 160, 1); 45 | --tonic-shadow: rgba(150, 150, 150, 0.25); 46 | --tonic-dark: rgba(54, 57, 61, 1); 47 | --tonic-disabled: rgba(152, 161, 175, 1); 48 | --tonic-button-text: rgba(54, 57, 61, 1); 49 | --tonic-button-shadow: rgba(0, 0, 0, 33%); 50 | --tonic-button-background: rgba(245, 245, 245, 1); 51 | --tonic-button-background-hover: rgba(230, 230, 230, 1); 52 | --tonic-button-background-focus: rgba(237, 237, 237, 1); 53 | --tonic-input-text: rgba(54, 57, 61, 1); 54 | --tonic-input-text-hover: rgba(228, 228, 228, 1); 55 | --tonic-input-border: rgba(201, 201, 201, 1); 56 | --tonic-input-border-hover: rgba(54, 57, 61, 1); 57 | --tonic-input-background: rgba(248, 248, 248, 1); 58 | --tonic-input-background-focus: rgba(238, 238, 238, 1); 59 | --tonic-border: rgba(224, 224, 224, 1); 60 | --tonic-border-accent: rgba(206, 206, 206, 1); 61 | --tonic-error: rgba(240, 102, 83, 1); 62 | --tonic-notification: rgba(240, 102, 83, 1); 63 | --tonic-danger: rgba(240, 102, 83, 1); 64 | --tonic-success: rgba(133, 178, 116, 1); 65 | --tonic-warning: rgba(249, 169, 103, 1); 66 | --tonic-info: rgba(153, 157, 160, 1); 67 | --tonic-overlay: rgba(255, 255, 255, 0.75); 68 | } 69 | } 70 | 71 | @media (prefers-color-scheme: dark) { 72 | body, *[theme="dark"] { 73 | --tonic-background: rgba(0, 0, 0, 1); 74 | --tonic-background-dark: rgba(26, 26, 26, 1); 75 | --tonic-window: rgba(32, 32, 32, 1); 76 | --tonic-accent: rgba(56, 185, 255, 1); 77 | --tonic-primary: rgba(242, 242, 242, 1); 78 | --tonic-secondary: rgba(195, 195, 195, 1); 79 | --tonic-medium: rgba(153, 157, 160, 1); 80 | --tonic-dark: rgba(41, 41, 41, 1); 81 | --tonic-shadow: rgba(0, 0, 0, 0.3); 82 | --tonic-disabled: rgba(170, 170, 170, 1); 83 | --tonic-button-text: rgba(255, 255, 255, 1); 84 | --tonic-button-shadow: rgba(0, 0, 0, 1); 85 | --tonic-button-background: rgba(74, 74, 74, 1); 86 | --tonic-button-background-hover: rgba(94, 94, 94, 1); 87 | --tonic-button-background-focus: rgba(84, 84, 84, 1); 88 | --tonic-input-text: rgba(255, 255, 255, 1); 89 | --tonic-input-text-hover: rgba(255, 255, 255, 1); 90 | --tonic-input-background: rgba(12, 12, 12, 1); 91 | --tonic-input-background-focus: rgba(18, 18, 18, 1); 92 | --tonic-input-border: rgba(80, 80, 80, 1); 93 | --tonic-input-border-hover: rgba(105, 105, 105, 1); 94 | --tonic-border: rgba(72, 72, 72, 1); 95 | --tonic-border-accent: rgba(90, 90, 90, 1); 96 | --tonic-error: rgba(240, 102, 83, 1); 97 | --tonic-notification: rgba(240, 102, 83, 1); 98 | --tonic-caution: rgba(240, 102, 83, 1); 99 | --tonic-success: rgba(133, 178, 116, 1); 100 | --tonic-warn: rgba(249, 169, 103, 1); 101 | --tonic-overlay: rgba(0, 0, 0, 0.40); 102 | } 103 | } 104 | 105 | /* Placeholder Styles */ 106 | body { 107 | height: 100vh; 108 | font: -apple-system-body; 109 | background-color: var(--tonic-window); 110 | color: var(--tonic-primary); 111 | } 112 | 113 | h1 { 114 | position: absolute; 115 | top: 50%; 116 | left: 50%; 117 | transform: translate(-50%, -50%); 118 | font: -apple-system-title0; 119 | font-size: 2.5em; 120 | } 121 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'node:fs/promises' 4 | import util from 'node:util' 5 | import path from 'node:path' 6 | import { exec as ecp, spawn } from 'node:child_process' 7 | import os from 'node:os' 8 | 9 | const exec = util.promisify(ecp) 10 | const __dirname = path.dirname(import.meta.url).replace(`file://${os.platform() === 'win32' ? '/' : ''}`, '') 11 | const DEFAULT_TEMPLATE = 'vanilla' 12 | 13 | async function copyFileOrFolder (source, target) { 14 | const stats = await fs.stat(source) 15 | 16 | if (stats.isFile()) { 17 | await fs.mkdir(path.dirname(target), { recursive: true }) 18 | await fs.copyFile(source, target) 19 | } else if (stats.isDirectory()) { 20 | await fs.mkdir(target, { recursive: true }) 21 | 22 | const files = await fs.readdir(source) 23 | 24 | for (const file of files) { 25 | const sourceFile = path.join(source, file) 26 | const targetFile = path.join(target, file) 27 | 28 | await copyFileOrFolder(sourceFile, targetFile) 29 | } 30 | } 31 | } 32 | 33 | const cp = async (a, b) => copyFileOrFolder( 34 | path.resolve(a), 35 | path.join(b, path.basename(a)) 36 | ) 37 | 38 | async function help (templateNames) { 39 | console.log(`usage: npm create socket-app [${templateNames.join(' | ')}]`) 40 | } 41 | 42 | async function install () { 43 | } 44 | 45 | const DEFAULT_DEPS = [ 46 | ] 47 | 48 | const DEFAULT_DEV_DEPS = [ 49 | ] 50 | 51 | const templates = {} 52 | 53 | templates.vanilla = { 54 | devDeps: ['esbuild'] 55 | } 56 | templates.tonic = { 57 | deps: ['@socketsupply/tonic'], 58 | devDeps: ['esbuild'] 59 | } 60 | templates.react = { 61 | deps: ['react', 'react-dom'], 62 | devDeps: ['esbuild'] 63 | } 64 | templates.vue = { 65 | deps: ['vue'], 66 | devDeps: ['vite', '@vitejs/plugin-vue'] 67 | } 68 | templates.svelte = { 69 | deps: ['svelte'], 70 | devDeps: ['vite', '@sveltejs/vite-plugin-svelte'] 71 | } 72 | templates['react-ts'] = { 73 | deps: ['react', 'react-dom', 'typescript', '@types/react', '@types/react-dom', '@types/node'], 74 | devDeps: ['esbuild'] 75 | } 76 | 77 | async function main (argv) { 78 | const templateName = argv[0] ?? DEFAULT_TEMPLATE 79 | 80 | const templateNames = await fs.readdir(path.join(__dirname, 'templates')) 81 | 82 | if (argv.find(s => s.includes('-h'))) { 83 | return help(templateNames) 84 | } 85 | 86 | if (templateName && templateNames.findIndex(s => s === templateName) === -1) { 87 | console.error(`Unable to find template "${templateName}"`) 88 | return help(templateNames) 89 | } 90 | 91 | // 92 | // Check if the ssc command is installed, if not install it. 93 | // 94 | try { 95 | await exec('ssc') 96 | } catch (err) { 97 | if (err.code === 127) await install() 98 | } 99 | 100 | // 101 | // If the current directory is not empty, refuse to initialize it. 102 | // Empty excludes the following list of files from the directory. 103 | // 104 | const accepted = [ 105 | '.DS_Store', 106 | '.git', 107 | '.gitattributes', 108 | '.gitignore', 109 | '.gitlab-ci.yml', 110 | '.hg', 111 | '.hgcheck', 112 | '.hgignore', 113 | '.idea', 114 | '.npmignore', 115 | '.travis.yml', 116 | 'docs', 117 | 'LICENSE', 118 | 'README.md', 119 | 'mkdocs.yml', 120 | 'Thumbs.db' 121 | ] 122 | 123 | try { 124 | const entries = (await fs.readdir(process.cwd())) 125 | .filter(file => !accepted.includes(file)) 126 | 127 | if (entries.length) { 128 | process.stdout.write('\nThe current directory is not empty\n') 129 | process.exit(1) 130 | } 131 | } catch (err) { 132 | process.stderr.write(`\nUnable to read the current directory: ${err.stack ?? err.message}\n`) 133 | process.exit(1) 134 | } 135 | 136 | // 137 | // Create a package.json that has the module and a basic build setup. 138 | // 139 | try { 140 | process.stdout.write('Initializing npm package...') 141 | await exec('npm init -y') 142 | } catch (err) { 143 | process.stderr.write(`Unable to run npm init: ${err.stack ?? err.message}\n`) 144 | process.exit(1) 145 | } 146 | process.stdout.write('Ok.\n') 147 | 148 | // 149 | // Install an opinionated base of modules for building a simple app. 150 | // 151 | const devDeps = [ 152 | ...DEFAULT_DEV_DEPS, 153 | ...templates[templateName]?.devDeps ?? [] 154 | ] 155 | 156 | if (devDeps.length > 0) { 157 | try { 158 | process.stdout.write('Installing developer dependencies...') 159 | await exec(`npm install -D ${devDeps.join(' ')}`) 160 | } catch (err) { 161 | process.stderr.write(`\nUnable to run npm install: ${err.stack ?? err.message}\n`) 162 | process.exit(1) 163 | } 164 | 165 | process.stdout.write('Ok.\n') 166 | } 167 | 168 | const deps = [ 169 | ...DEFAULT_DEPS, 170 | ...templates[templateName]?.deps ?? [] 171 | ] 172 | 173 | // remove eventually 174 | let isSocket05orGreater = true 175 | 176 | try { 177 | const { stdout } = await exec('ssc --version') 178 | 179 | try { 180 | const sscVersion = stdout.trim().split(' ')[0] 181 | // split by dot 182 | .split('.') 183 | // convert to numbers 184 | .map(s => parseInt(s)) 185 | 186 | isSocket05orGreater = sscVersion[0] >= 1 || sscVersion[1] >= 5 187 | } catch (err) {} 188 | } catch (err) { 189 | process.stdout.write('Installing \'@socketsupply/socket\' locally (ssc not in PATH)\n') 190 | deps.push('@socketsupply/socket') 191 | } 192 | 193 | try { 194 | process.stdout.write('Installing dependencies...') 195 | await exec(`npm install ${deps.join(' ')} --save`) 196 | } catch (err) { 197 | process.stderr.write(`\nUnable to run npm install: ${err.stack ?? err.message}\n`) 198 | process.exit(1) 199 | } 200 | process.stdout.write('Ok.\n') 201 | 202 | process.stdout.write('Adding package scripts...') 203 | let pkg 204 | 205 | try { 206 | pkg = JSON.parse(await fs.readFile('package.json')) 207 | } catch (err) { 208 | process.stderr.write(`\nUnable to read package.json: ${err.stack ?? err.message}\n`) 209 | process.exit(1) 210 | } 211 | 212 | pkg.type = 'module' 213 | pkg.scripts['init-project'] = `ssc init${isSocket05orGreater ? ' --config' : ''}` 214 | pkg.scripts.start = 'ssc build -r -o' 215 | pkg.scripts.build = 'ssc build -o' 216 | pkg.scripts.test = 'ssc build -r -o --test=./test/index.js --headless' 217 | 218 | try { 219 | fs.writeFile('package.json', JSON.stringify(pkg, 2, 2)) 220 | } catch (err) { 221 | process.stderr.write(`\nUnable to write package.json: ${err.stack ?? err.message}\n`) 222 | process.exit(1) 223 | } 224 | 225 | process.stdout.write('Ok.\n') 226 | 227 | // 228 | // Initialize the current directory as a socket app. 229 | // 230 | try { 231 | process.stdout.write('Creating socket files...') 232 | // Use spawn so we can pass stdio, fte is interactive 233 | const initProcess = spawn( 234 | `npm${os.platform() === 'win32' ? '.cmd' : ''}`, 235 | ['run', 'init-project'], 236 | { 237 | stdio: [process.stdin, process.stdout, process.stderr] 238 | }) 239 | await new Promise((resolve, reject) => { 240 | initProcess.on('close', resolve).on('error', reject) 241 | }) 242 | } catch (err) { 243 | process.stderr.write(`\nUnable to create socket files: ${err.stack ?? err.message}\n`) 244 | } 245 | process.stdout.write('Ok.\n') 246 | 247 | // 248 | // Initialize tsconfig.json when react_ts 249 | // 250 | if (templateName === 'react-ts') { 251 | try { 252 | process.stdout.write('Creating tsconfig...') 253 | await exec( 254 | 'npx tsc --init --declaration --allowJs --emitDeclarationOnly --jsx react-jsx --lib "dom","dom.iterable","esnext" --outDir dist' 255 | ) 256 | } catch (err) { 257 | process.stderr.write( 258 | `\nFailed to create tsconfig: ${err.stack ?? err.message}\n` 259 | ) 260 | process.exit(1) 261 | } 262 | 263 | process.stdout.write('Ok.\n') 264 | 265 | try { 266 | process.stdout.write('Setting up TS configuration...') 267 | await fs.writeFile( 268 | 'globals.d.ts', 269 | "declare module 'socket:os'; \ndeclare module 'socket:test'; \ndeclare module 'socket:console'; \ndeclare module 'socket:process';" 270 | ) 271 | } catch (err) { 272 | process.stderr.write( 273 | `Failed to create global.d.ts: ${ 274 | err.stack ?? err.message 275 | }.Please report this issue here: https://github.com/socketsupply/create-socket-app/issues\n` 276 | ) 277 | } 278 | 279 | process.stdout.write('Ok.\n') 280 | } 281 | 282 | let config 283 | process.stdout.write('Updating project configuration...') 284 | 285 | try { 286 | config = await fs.readFile('socket.ini', 'utf8') 287 | } catch (err) { 288 | process.stderr.write(`\nUnable to read socket.ini: ${err.stack ?? err.message}\n`) 289 | process.exit(1) 290 | } 291 | 292 | config = config.split('\n').map((line, i) => { 293 | if (line.includes('name = ')) { 294 | return line.replace(line, `name = "${pkg.name}"`) 295 | } 296 | if (line.includes('copy = ') && !line.startsWith(';')) { 297 | return line.replace(line, `; ${line}`) 298 | } 299 | if (line.includes('script = ')) { 300 | return line.replace(line, 'script = "node build.js"') 301 | } 302 | // Socket 0.5 compatibility 303 | if (isSocket05orGreater && line.includes('forward_arguments = ')) { 304 | return line.replace(line, 'forward_arguments = true') 305 | } 306 | return line 307 | }).join('\n') 308 | 309 | try { 310 | await fs.writeFile('socket.ini', config) 311 | } catch (err) { 312 | process.stderr.write(`\nUnable to write socket.ini: ${err.stack ?? err.message}\n`) 313 | process.exit(1) 314 | } 315 | process.stdout.write('Ok.\n') 316 | 317 | process.stdout.write('Copying project boilerplate...') 318 | 319 | const dirsToCopy = [ 320 | 'common', 321 | `templates/${templateName}` 322 | ] 323 | 324 | let filesToCopy 325 | try { 326 | const filesInGroups = await Promise.all(dirsToCopy.map(dir => fs.readdir(path.join(__dirname, dir)))) 327 | filesToCopy = filesInGroups.map((group, i) => group.map(file => path.join(__dirname, dirsToCopy[i], file))).flat() 328 | } catch (err) { 329 | process.stderr.write(`\nUnable to read template files: ${err.stack ?? err.message}\n`) 330 | } 331 | 332 | try { 333 | await Promise.all(filesToCopy.map(dir => cp(dir, process.cwd()))) 334 | } catch (err) { 335 | process.stderr.write(`\nUnable to copy files: ${err.stack ?? err.message}\n`) 336 | process.exit(1) 337 | } 338 | process.stdout.write('Ok.') 339 | 340 | process.stdout.write('\n\nType \'npm start\' to launch the app\n') 341 | } 342 | 343 | main(process.argv.slice(2)) 344 | --------------------------------------------------------------------------------