@surplus/create-surplus-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +37 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/workflows/push.yml +20 -0
- package/.prettierignore +1 -0
- package/LICENSE.txt +19 -0
- package/README.md +12 -0
- package/cli.mjs +176 -0
- package/package.json +23 -0
- package/template/.editorconfig +37 -0
- package/template/esbuild.mjs +19 -0
- package/template/package.json +21 -0
- package/template/src/app.jsx +50 -0
- package/template/src/index.html +12 -0
- package/template/src/styles.css +17 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
indent_style = tab
|
|
5
|
+
indent_size = 4
|
|
6
|
+
tab_width = 4
|
|
7
|
+
end_of_line = lf
|
|
8
|
+
charset = utf-8
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
|
|
12
|
+
[{*.gyp,*.yml,*.yaml}]
|
|
13
|
+
indent_style = space
|
|
14
|
+
indent_size = 2
|
|
15
|
+
|
|
16
|
+
[{*.py,*.asm}]
|
|
17
|
+
indent_style = space
|
|
18
|
+
|
|
19
|
+
[*.py]
|
|
20
|
+
indent_size = 4
|
|
21
|
+
|
|
22
|
+
[*.asm]
|
|
23
|
+
indent_size = 8
|
|
24
|
+
|
|
25
|
+
[{*.md,*.tsv}]
|
|
26
|
+
trim_trailing_whitespace = false
|
|
27
|
+
|
|
28
|
+
# Ideal settings - some plugins might support these.
|
|
29
|
+
[*.js]
|
|
30
|
+
quote_type = single
|
|
31
|
+
|
|
32
|
+
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
|
33
|
+
curly_bracket_next_line = false
|
|
34
|
+
spaces_around_operators = true
|
|
35
|
+
spaces_around_brackets = outside
|
|
36
|
+
# close enough to 1TB
|
|
37
|
+
indent_brace_style = K&R
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: "qix-"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Lint + Test
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
submodules: "true"
|
|
15
|
+
- name: Install pnpm
|
|
16
|
+
run: npm install -g pnpm
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: pnpm install
|
|
19
|
+
- name: Lint
|
|
20
|
+
run: pnpm lint
|
package/.prettierignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/pnpm-lock.yaml
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Josh Junon
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# `npx create-surplus-app`
|
|
2
|
+
|
|
3
|
+
This repository is the package for `@surplus/create-surplus-app`, and
|
|
4
|
+
hosts the `create-surplus-app` binary.
|
|
5
|
+
|
|
6
|
+
```shell
|
|
7
|
+
$ npx create-surplus-app --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
# License
|
|
11
|
+
|
|
12
|
+
Copyright © 2026, Joshua Lee Junon. Released under the [MIT License](LICENSE.txt).
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chalkTemplateStderr as chalk } from "chalk-template";
|
|
3
|
+
import arg from "arg";
|
|
4
|
+
import packageJson from "./package.json" with { type: "json" };
|
|
5
|
+
import child_process from "node:child_process";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fsp from "node:fs/promises";
|
|
8
|
+
import { URL } from "node:url";
|
|
9
|
+
|
|
10
|
+
function showHelp() {
|
|
11
|
+
console.error(chalk`
|
|
12
|
+
{bold.ansi256(92) create-surplus-app} - Create a new Surplus application.
|
|
13
|
+
|
|
14
|
+
{bold USAGE}
|
|
15
|
+
{bold.ansi256(92) pnpx create-surplus-app} [--use-npm] [{underline PROJECT_DIR}]
|
|
16
|
+
{bold.ansi256(92) pnpx create-surplus-app} [--help] [--version]
|
|
17
|
+
|
|
18
|
+
{bold OPTIONS}
|
|
19
|
+
--use-npm Use npm as the package manager
|
|
20
|
+
(default is pnpm or yarn, if available, in that order)
|
|
21
|
+
--allow-dirty Allow creating the app in a non-empty directory
|
|
22
|
+
--help Show this help message
|
|
23
|
+
--version Show the version number
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function showVersion() {
|
|
30
|
+
console.error(`create-surplus-app version ${packageJson.version}`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const args = arg({
|
|
35
|
+
"--help": Boolean,
|
|
36
|
+
"--version": Boolean,
|
|
37
|
+
"--use-npm": Boolean,
|
|
38
|
+
"--allow-dirty": Boolean,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (args["--help"]) showHelp();
|
|
42
|
+
if (args["--version"]) showVersion();
|
|
43
|
+
|
|
44
|
+
const packageManager = args["--use-npm"]
|
|
45
|
+
? "npm"
|
|
46
|
+
: determineBestPackageManager();
|
|
47
|
+
|
|
48
|
+
function determineBestPackageManager() {
|
|
49
|
+
try {
|
|
50
|
+
const pnpmVersion = child_process
|
|
51
|
+
.execSync("pnpm --version", { stdio: "pipe" })
|
|
52
|
+
.toString()
|
|
53
|
+
.trim();
|
|
54
|
+
if (pnpmVersion) return "pnpm";
|
|
55
|
+
} catch {}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const yarnVersion = child_process
|
|
59
|
+
.execSync("yarn --version", { stdio: "pipe" })
|
|
60
|
+
.toString()
|
|
61
|
+
.trim();
|
|
62
|
+
if (yarnVersion) return "yarn";
|
|
63
|
+
} catch {}
|
|
64
|
+
|
|
65
|
+
return "npm";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const projectDir = args._[0] || ".";
|
|
69
|
+
const projectPath = path.resolve(process.cwd(), projectDir);
|
|
70
|
+
const projectParent = path.dirname(projectPath);
|
|
71
|
+
const projectName = path.basename(projectPath);
|
|
72
|
+
|
|
73
|
+
console.error(chalk`
|
|
74
|
+
Creating a new {bold.ansi256(92) Surplus} app in {bold ${projectPath}} using {bold ${packageManager}}...
|
|
75
|
+
`);
|
|
76
|
+
|
|
77
|
+
// Make sure the parent directory exists
|
|
78
|
+
if (
|
|
79
|
+
!(await fsp
|
|
80
|
+
.stat(projectParent)
|
|
81
|
+
.then((s) => s.isDirectory())
|
|
82
|
+
.catch(() => false))
|
|
83
|
+
) {
|
|
84
|
+
console.error(
|
|
85
|
+
chalk` {bold.ansi256(196) Error:} The parent directory {bold ${projectParent}} does not exist or is not a directory.`,
|
|
86
|
+
);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Make sure the project directory doesn't exist as a non-directory
|
|
91
|
+
if (
|
|
92
|
+
await fsp
|
|
93
|
+
.stat(projectPath)
|
|
94
|
+
.then((s) => !s.isDirectory())
|
|
95
|
+
.catch(() => false)
|
|
96
|
+
) {
|
|
97
|
+
console.error(
|
|
98
|
+
chalk` {bold.ansi256(196) Error:} The path {bold ${projectPath}} exists and is not a directory.`,
|
|
99
|
+
);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create it if it doesn't exist
|
|
104
|
+
// Recursive is safe here because we verified the parent exists.
|
|
105
|
+
await fsp.mkdir(projectPath, { recursive: true });
|
|
106
|
+
|
|
107
|
+
// Make sure the directory is empty
|
|
108
|
+
if (
|
|
109
|
+
!(await fsp.readdir(projectPath).then((f) => f.length === 0)) &&
|
|
110
|
+
!args["--allow-dirty"]
|
|
111
|
+
) {
|
|
112
|
+
console.error(chalk` {bold.ansi256(196) Error:} The directory {bold ${projectPath}} is not empty.
|
|
113
|
+
If you'd like to create a new Surplus app here, please empty the directory first,
|
|
114
|
+
or specify {bold --allow-dirty} to proceed anyway.`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Discover all files in the template directory
|
|
119
|
+
const templateDir = path.resolve(
|
|
120
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
121
|
+
"template",
|
|
122
|
+
);
|
|
123
|
+
async function* getFiles(dir) {
|
|
124
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const fullPath = path.join(dir, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
yield* getFiles(fullPath);
|
|
129
|
+
} else if (entry.isFile()) {
|
|
130
|
+
yield fullPath;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Set up the template replacement values.
|
|
136
|
+
const templateValues = {
|
|
137
|
+
PKG_NAME: projectName,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const replaceTemplateValues = (content) =>
|
|
141
|
+
content.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => {
|
|
142
|
+
if (key in templateValues) {
|
|
143
|
+
return templateValues[key];
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`Unknown template key: ${key}`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Copy all files from the template directory to the project directory
|
|
149
|
+
for await (const filePath of getFiles(templateDir)) {
|
|
150
|
+
const relativePath = path.relative(templateDir, filePath);
|
|
151
|
+
const destPath = path.join(projectPath, relativePath);
|
|
152
|
+
|
|
153
|
+
// Ensure the destination directory exists
|
|
154
|
+
await fsp.mkdir(path.dirname(destPath), { recursive: true });
|
|
155
|
+
|
|
156
|
+
// Read the file content
|
|
157
|
+
let content = await fsp.readFile(filePath, "utf8");
|
|
158
|
+
|
|
159
|
+
// Replace template values
|
|
160
|
+
content = replaceTemplateValues(content);
|
|
161
|
+
|
|
162
|
+
// Write the file to the destination
|
|
163
|
+
await fsp.writeFile(destPath, content, "utf8");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.error(chalk` {bold.ansi256(92) Success:} Created a new Surplus app in {bold ${projectPath}}!
|
|
167
|
+
|
|
168
|
+
If you have any questions or problems:
|
|
169
|
+
- {bold Ask a question:} https://github.com/surplus/create-surplus-app/discussions
|
|
170
|
+
- {bold Report a bug:} https://github.com/surplus/create-surplus-app/issues
|
|
171
|
+
|
|
172
|
+
You can now run:
|
|
173
|
+
{bold cd ${projectDir}}
|
|
174
|
+
{bold ${packageManager} install}
|
|
175
|
+
{bold ${packageManager} run dev}
|
|
176
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@surplus/create-surplus-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "npx create-surplus-app - create a Surplus application in seconds",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-surplus-app": "./cli.mjs"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"lint": "prettier -c .",
|
|
10
|
+
"format": "prettier -w ."
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "Joshua Lee Junon (https://github.com/qix-)",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"prettier": "^3.7.4"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"arg": "^5.0.2",
|
|
20
|
+
"chalk": "^5.6.2",
|
|
21
|
+
"chalk-template": "^1.1.2"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
indent_style = tab
|
|
5
|
+
indent_size = 4
|
|
6
|
+
tab_width = 4
|
|
7
|
+
end_of_line = lf
|
|
8
|
+
charset = utf-8
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
|
|
12
|
+
[{*.gyp,*.yml,*.yaml}]
|
|
13
|
+
indent_style = space
|
|
14
|
+
indent_size = 2
|
|
15
|
+
|
|
16
|
+
[{*.py,*.asm}]
|
|
17
|
+
indent_style = space
|
|
18
|
+
|
|
19
|
+
[*.py]
|
|
20
|
+
indent_size = 4
|
|
21
|
+
|
|
22
|
+
[*.asm]
|
|
23
|
+
indent_size = 8
|
|
24
|
+
|
|
25
|
+
[{*.md,*.tsv}]
|
|
26
|
+
trim_trailing_whitespace = false
|
|
27
|
+
|
|
28
|
+
# Ideal settings - some plugins might support these.
|
|
29
|
+
[*.js]
|
|
30
|
+
quote_type = single
|
|
31
|
+
|
|
32
|
+
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
|
33
|
+
curly_bracket_next_line = false
|
|
34
|
+
spaces_around_operators = true
|
|
35
|
+
spaces_around_brackets = outside
|
|
36
|
+
# close enough to 1TB
|
|
37
|
+
indent_brace_style = K&R
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import esbuild from "esbuild";
|
|
2
|
+
import surplus from "@surplus/esbuild";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
6
|
+
|
|
7
|
+
// Build JSX application
|
|
8
|
+
await esbuild.build({
|
|
9
|
+
entryPoints: ["src/app.jsx"],
|
|
10
|
+
bundle: true,
|
|
11
|
+
minify: isProduction,
|
|
12
|
+
sourcemap: !isProduction,
|
|
13
|
+
outfile: "pkg/app.js",
|
|
14
|
+
plugins: [surplus()],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Copy static files
|
|
18
|
+
await fsp.copyFile("src/index.html", "pkg/index.html");
|
|
19
|
+
await fsp.copyFile("src/styles.css", "pkg/styles.css");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PKG_NAME}}",
|
|
3
|
+
"description": "A Surplus application",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "tsc && prettier -c .",
|
|
8
|
+
"format": "prettier -w .",
|
|
9
|
+
"build": "env NODE_ENV=production node esbuild.mjs",
|
|
10
|
+
"dev": "node esbuild.mjs"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@surplus/s": "^1.2.0",
|
|
14
|
+
"@surplus/esbuild": "^1.2.1",
|
|
15
|
+
"@surplus/css": "^0.1.0",
|
|
16
|
+
"@surplus/types": "^0.1.3",
|
|
17
|
+
"esbuild": "^0.27.2",
|
|
18
|
+
"prettier": "^3.7.4",
|
|
19
|
+
"tsc": "^2.0.4"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import S from "@surplus/s";
|
|
2
|
+
|
|
3
|
+
const Calculator = () => {
|
|
4
|
+
const formula = S.data("2 * (2 * 2 + 2) * 2 * 2 - 2 * 2 - 2");
|
|
5
|
+
const calculation = S(() => {
|
|
6
|
+
const f = formula();
|
|
7
|
+
if (!f) return 0;
|
|
8
|
+
if (/[^0-9+\-*/(). ]/.test(f)) return false;
|
|
9
|
+
// eslint-disable-next-line no-eval
|
|
10
|
+
return eval(f);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
let input;
|
|
14
|
+
return (
|
|
15
|
+
<div class="calculator">
|
|
16
|
+
<label for="formula">Enter a formula:</label>
|
|
17
|
+
<input
|
|
18
|
+
type="text"
|
|
19
|
+
value={S.sample(formula)}
|
|
20
|
+
ref={input}
|
|
21
|
+
on:input={() => formula(input.value)}
|
|
22
|
+
/>
|
|
23
|
+
<span class="result">
|
|
24
|
+
= {calculation() !== false ? calculation() : undefined}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Root = () => (
|
|
31
|
+
<div id="root">
|
|
32
|
+
<div id="content">
|
|
33
|
+
<h1>
|
|
34
|
+
Welcome to your Surplus application,{" "}
|
|
35
|
+
<strong>{{ PKG_NAME }}</strong>!
|
|
36
|
+
</h1>
|
|
37
|
+
<p>
|
|
38
|
+
You can start editing the code in <code>src/app.jsx</code> to
|
|
39
|
+
build your application.
|
|
40
|
+
</p>
|
|
41
|
+
<p>
|
|
42
|
+
For example, here's a calculator that updates in real-time:
|
|
43
|
+
<Calculator />
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Mount the root component to the document body
|
|
50
|
+
S.root(() => document.body.prepend(<Root />));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>My App</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<script type="module" src="app.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
html,
|
|
2
|
+
body {
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
font-family: Arial, sans-serif;
|
|
6
|
+
background-color: black;
|
|
7
|
+
color: white;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
div#root {
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
display: flex;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|