├── 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 |
12 | {{ msg }}
13 |
14 |
15 |
16 |
17 |
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 |
8 |
9 |
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 |
--------------------------------------------------------------------------------