create-wpt-iot 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/README.md +71 -0
- package/dist/artifact.d.ts +13 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +37 -0
- package/dist/artifact.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +33 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +175 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-file.d.ts +12 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +35 -0
- package/dist/config-file.js.map +1 -0
- package/dist/i18n.d.ts +7 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +10 -0
- package/dist/i18n.js.map +1 -0
- package/dist/installer-manifest.d.ts +3 -0
- package/dist/installer-manifest.d.ts.map +1 -0
- package/dist/installer-manifest.js +7 -0
- package/dist/installer-manifest.js.map +1 -0
- package/dist/local.d.ts +6 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +42 -0
- package/dist/local.js.map +1 -0
- package/dist/messages/en.d.ts +48 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +48 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/it.d.ts +3 -0
- package/dist/messages/it.d.ts.map +1 -0
- package/dist/messages/it.js +48 -0
- package/dist/messages/it.js.map +1 -0
- package/dist/preflight.d.ts +5 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +25 -0
- package/dist/preflight.js.map +1 -0
- package/dist/process.d.ts +39 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +115 -0
- package/dist/process.js.map +1 -0
- package/dist/prompts.d.ts +36 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +65 -0
- package/dist/prompts.js.map +1 -0
- package/dist/remote.d.ts +8 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +211 -0
- package/dist/remote.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +12 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +69 -0
- package/dist/validation.js.map +1 -0
- package/package.json +44 -0
package/dist/remote.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, copyFile, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { isIP } from 'node:net';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { assertSufficientDiskSpace, normalizeArchitecture } from './preflight.js';
|
|
7
|
+
import { validateHost, validatePort, validateUsername } from './validation.js';
|
|
8
|
+
const REMOTE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
9
|
+
const REMOTE_PROBE = `set -eu
|
|
10
|
+
exec 2>&1
|
|
11
|
+
INSTALL_DIR="$(printf '%s' "$1" | base64 --decode)"
|
|
12
|
+
case "$INSTALL_DIR" in
|
|
13
|
+
/*) ;;
|
|
14
|
+
*) echo 'invalid install directory' >&2; exit 1 ;;
|
|
15
|
+
esac
|
|
16
|
+
[ "$INSTALL_DIR" != "/" ] || { echo 'invalid install directory' >&2; exit 1; }
|
|
17
|
+
[ "$(uname -s)" = "Linux" ] || { echo 'Linux target required' >&2; exit 1; }
|
|
18
|
+
for REQUIRED_COMMAND in apt-get systemctl sudo curl; do
|
|
19
|
+
command -v "$REQUIRED_COMMAND" >/dev/null 2>&1 || {
|
|
20
|
+
echo "missing command: $REQUIRED_COMMAND" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
23
|
+
done
|
|
24
|
+
ARCHITECTURE="$(uname -m)"
|
|
25
|
+
case "$ARCHITECTURE" in
|
|
26
|
+
aarch64|arm64|x86_64|amd64) ;;
|
|
27
|
+
*) echo "unsupported architecture: $ARCHITECTURE" >&2; exit 1 ;;
|
|
28
|
+
esac
|
|
29
|
+
EXISTING_INSTALL=false
|
|
30
|
+
[ -f "$INSTALL_DIR/docker-compose.yml" ] && EXISTING_INSTALL=true
|
|
31
|
+
DETECTED_SERIAL=""
|
|
32
|
+
[ ! -r /etc/wpt/serial ] || DETECTED_SERIAL="$(tr -d '\\n\\r \\t' < /etc/wpt/serial)"
|
|
33
|
+
DISK_AVAILABLE_KB="$(df -Pk / | awk 'NR == 2 { print $4 }')"
|
|
34
|
+
case "$DISK_AVAILABLE_KB" in
|
|
35
|
+
''|*[!0-9]*) echo 'unable to determine disk availability' >&2; exit 1 ;;
|
|
36
|
+
esac
|
|
37
|
+
for URL in https://ghcr.io https://raw.githubusercontent.com https://get.docker.com; do
|
|
38
|
+
curl -fsS -o /dev/null -m 10 "$URL"
|
|
39
|
+
done
|
|
40
|
+
printf 'architecture=%s\\n' "$ARCHITECTURE"
|
|
41
|
+
printf 'existing_install=%s\\n' "$EXISTING_INSTALL"
|
|
42
|
+
printf 'detected_serial_base64=%s\\n' "$(printf '%s' "$DETECTED_SERIAL" | base64 | tr -d '\\n')"
|
|
43
|
+
printf 'disk_available_kb=%s\\n' "$DISK_AVAILABLE_KB"
|
|
44
|
+
`;
|
|
45
|
+
const STALE_CLEANUP = `set -eu
|
|
46
|
+
exec 2>&1
|
|
47
|
+
find /tmp -xdev -maxdepth 1 -type f -user "$(id -un)" -mtime +0 \\
|
|
48
|
+
\\( -name 'create-wpt-iot-????????-????-????-????-????????????-installer.sh' \\
|
|
49
|
+
-o -name 'create-wpt-iot-????????-????-????-????-????????????-install.conf' \\
|
|
50
|
+
-o -name 'create-wpt-iot-????????-????-????-????-????????????-run.sh' \\) \\
|
|
51
|
+
-delete
|
|
52
|
+
`;
|
|
53
|
+
const REMOTE_INSTALL = `#!/bin/sh
|
|
54
|
+
set -eu
|
|
55
|
+
exec 2>&1
|
|
56
|
+
INSTALLER="$1"
|
|
57
|
+
CONFIG="$2"
|
|
58
|
+
RUNNER="$0"
|
|
59
|
+
cleanup() { rm -f -- "$CONFIG" "$INSTALLER" "$RUNNER"; }
|
|
60
|
+
trap cleanup EXIT
|
|
61
|
+
trap 'exit 129' HUP
|
|
62
|
+
trap 'exit 130' INT
|
|
63
|
+
trap 'exit 143' TERM
|
|
64
|
+
chmod 700 "$INSTALLER"
|
|
65
|
+
chmod 600 "$CONFIG"
|
|
66
|
+
sudo bash "$INSTALLER" --config "$CONFIG"
|
|
67
|
+
`;
|
|
68
|
+
const CURRENT_CLEANUP = `set -eu
|
|
69
|
+
exec 2>&1
|
|
70
|
+
rm -f -- "$1" "$2" "$3"
|
|
71
|
+
`;
|
|
72
|
+
function remoteScriptCommand(script, args = []) {
|
|
73
|
+
const encodedScript = Buffer.from(script, 'utf8').toString('base64');
|
|
74
|
+
const suffix = args.length === 0 ? '' : ` -- ${args.join(' ')}`;
|
|
75
|
+
return `printf %s ${encodedScript} | base64 --decode | sh -s${suffix}`;
|
|
76
|
+
}
|
|
77
|
+
async function stageRemoteFiles(artifact, config, remoteId, tempRoot) {
|
|
78
|
+
const directory = await mkdtemp(join(tempRoot, 'create-wpt-iot-upload-'));
|
|
79
|
+
const installerPath = join(directory, `create-wpt-iot-${remoteId}-installer.sh`);
|
|
80
|
+
const configPath = join(directory, `create-wpt-iot-${remoteId}-install.conf`);
|
|
81
|
+
const runnerPath = join(directory, `create-wpt-iot-${remoteId}-run.sh`);
|
|
82
|
+
try {
|
|
83
|
+
await chmod(directory, 0o700);
|
|
84
|
+
await copyFile(artifact.path, installerPath);
|
|
85
|
+
await copyFile(config.path, configPath);
|
|
86
|
+
await writeFile(runnerPath, REMOTE_INSTALL, { mode: 0o700, flag: 'wx' });
|
|
87
|
+
await chmod(installerPath, 0o700);
|
|
88
|
+
await chmod(configPath, 0o600);
|
|
89
|
+
await chmod(runnerPath, 0o700);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
await rm(directory, { recursive: true, force: true });
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
path: directory,
|
|
97
|
+
installerPath,
|
|
98
|
+
configPath,
|
|
99
|
+
runnerPath,
|
|
100
|
+
cleanup: async () => rm(directory, { recursive: true, force: true }),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function sshDestination(target) {
|
|
104
|
+
const username = validateUsername(target.username);
|
|
105
|
+
const host = validateHost(target.host);
|
|
106
|
+
return `${username}@${isIP(host) === 6 ? `[${host}]` : host}`;
|
|
107
|
+
}
|
|
108
|
+
function parseProbeOutput(output) {
|
|
109
|
+
const values = new Map();
|
|
110
|
+
for (const line of output.split(/\r?\n/)) {
|
|
111
|
+
const separator = line.indexOf('=');
|
|
112
|
+
if (separator <= 0)
|
|
113
|
+
continue;
|
|
114
|
+
values.set(line.slice(0, separator), line.slice(separator + 1));
|
|
115
|
+
}
|
|
116
|
+
const rawArchitecture = values.get('architecture');
|
|
117
|
+
const rawExisting = values.get('existing_install');
|
|
118
|
+
const rawSerial = values.get('detected_serial_base64');
|
|
119
|
+
const rawDisk = values.get('disk_available_kb');
|
|
120
|
+
if (rawArchitecture === undefined
|
|
121
|
+
|| !['true', 'false'].includes(rawExisting ?? '')
|
|
122
|
+
|| rawSerial === undefined
|
|
123
|
+
|| rawDisk === undefined
|
|
124
|
+
|| !/^\d+$/.test(rawDisk)) {
|
|
125
|
+
throw new Error('invalidRemotePreflightOutput');
|
|
126
|
+
}
|
|
127
|
+
assertSufficientDiskSpace(rawDisk);
|
|
128
|
+
if (rawSerial !== '' && !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(rawSerial)) {
|
|
129
|
+
throw new Error('invalidRemotePreflightOutput');
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
architecture: normalizeArchitecture(rawArchitecture),
|
|
133
|
+
existingInstall: rawExisting === 'true',
|
|
134
|
+
detectedSerial: Buffer.from(rawSerial, 'base64').toString('utf8').trim(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function preflightRemote(runner, target, installDir, platform = process.platform) {
|
|
138
|
+
await runner.run('ssh', ['-V']);
|
|
139
|
+
if (platform === 'win32') {
|
|
140
|
+
await runner.run('where.exe', ['scp.exe']);
|
|
141
|
+
}
|
|
142
|
+
else if (platform === 'linux') {
|
|
143
|
+
await runner.run('sh', [
|
|
144
|
+
'-c',
|
|
145
|
+
'command -v "$1" >/dev/null 2>&1',
|
|
146
|
+
'create-wpt-iot',
|
|
147
|
+
'scp',
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
throw new Error('unsupportedRemoteClientPlatform');
|
|
152
|
+
}
|
|
153
|
+
const result = await runner.run('ssh', [
|
|
154
|
+
'-p',
|
|
155
|
+
String(validatePort(String(target.port))),
|
|
156
|
+
sshDestination(target),
|
|
157
|
+
remoteScriptCommand(REMOTE_PROBE, [Buffer.from(installDir, 'utf8').toString('base64')]),
|
|
158
|
+
], { terminal: true });
|
|
159
|
+
return parseProbeOutput(result.stdout);
|
|
160
|
+
}
|
|
161
|
+
export async function installRemote(runner, target, artifact, config, settings, remoteId = randomUUID(), warn = (message) => process.stderr.write(`${message}\n`), tempRoot = tmpdir()) {
|
|
162
|
+
if (!REMOTE_ID_PATTERN.test(remoteId))
|
|
163
|
+
throw new Error('invalidRemoteId');
|
|
164
|
+
const destination = sshDestination(target);
|
|
165
|
+
const port = String(validatePort(String(target.port)));
|
|
166
|
+
const installerPath = `/tmp/create-wpt-iot-${remoteId}-installer.sh`;
|
|
167
|
+
const configPath = `/tmp/create-wpt-iot-${remoteId}-install.conf`;
|
|
168
|
+
const runnerPath = `/tmp/create-wpt-iot-${remoteId}-run.sh`;
|
|
169
|
+
const redactions = [settings.adminPassword];
|
|
170
|
+
await runner.run('ssh', ['-p', port, destination, remoteScriptCommand(STALE_CLEANUP)], { terminal: true, redactions });
|
|
171
|
+
const staged = await stageRemoteFiles(artifact, config, remoteId, tempRoot);
|
|
172
|
+
let operationFailed = false;
|
|
173
|
+
let operationError;
|
|
174
|
+
try {
|
|
175
|
+
await runner.run('scp', [
|
|
176
|
+
'-P',
|
|
177
|
+
port,
|
|
178
|
+
staged.installerPath,
|
|
179
|
+
staged.configPath,
|
|
180
|
+
staged.runnerPath,
|
|
181
|
+
`${destination}:/tmp/`,
|
|
182
|
+
], { terminal: true, redactions });
|
|
183
|
+
await runner.run('ssh', ['-tt', '-p', port, destination, 'sh', runnerPath, installerPath, configPath], { terminal: true, redactions });
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
operationFailed = true;
|
|
187
|
+
operationError = error;
|
|
188
|
+
try {
|
|
189
|
+
await runner.run('ssh', [
|
|
190
|
+
'-p',
|
|
191
|
+
port,
|
|
192
|
+
destination,
|
|
193
|
+
remoteScriptCommand(CURRENT_CLEANUP, [installerPath, configPath, runnerPath]),
|
|
194
|
+
], { terminal: true, redactions });
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
warn('remoteCleanupFailed');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
await staged.cleanup();
|
|
202
|
+
}
|
|
203
|
+
catch (cleanupError) {
|
|
204
|
+
warn('localStagingCleanupFailed');
|
|
205
|
+
if (!operationFailed)
|
|
206
|
+
throw cleanupError;
|
|
207
|
+
}
|
|
208
|
+
if (operationFailed)
|
|
209
|
+
throw operationError;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type InstallMode = 'local' | 'remote';
|
|
2
|
+
export interface RemoteTarget {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
username: string;
|
|
6
|
+
}
|
|
7
|
+
export interface InstallSettings {
|
|
8
|
+
installDir: string;
|
|
9
|
+
deviceSerial: string;
|
|
10
|
+
enableAutoUpdate: boolean;
|
|
11
|
+
adminPassword?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PreflightResult {
|
|
14
|
+
architecture: 'arm64' | 'amd64';
|
|
15
|
+
existingInstall: boolean;
|
|
16
|
+
detectedSerial: string;
|
|
17
|
+
}
|
|
18
|
+
export interface InstallRequest {
|
|
19
|
+
mode: InstallMode;
|
|
20
|
+
remote?: RemoteTarget;
|
|
21
|
+
settings: InstallSettings;
|
|
22
|
+
preflight: PreflightResult;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,eAAe,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class ValidationError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
constructor(code: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function validateHost(raw: string): string;
|
|
6
|
+
export declare function validatePort(raw: string): number;
|
|
7
|
+
export declare function validateUsername(raw: string): string;
|
|
8
|
+
export declare function validateInstallDir(raw: string): string;
|
|
9
|
+
export declare function validateDeviceSerial(raw: string): string;
|
|
10
|
+
export declare function validateAdminPassword(raw: string): string;
|
|
11
|
+
export declare function confirmAdminPassword(password: string, confirmation: string): string;
|
|
12
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAGA,qBAAa,eAAgB,SAAQ,KAAK;aACZ,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC;AAMD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAGnF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
import { posix } from 'node:path';
|
|
3
|
+
export class ValidationError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code) {
|
|
6
|
+
super(code);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = 'ValidationError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const hostnamePattern = /^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?:\.(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/;
|
|
12
|
+
const usernamePattern = /^[a-z_][a-z0-9_-]{0,31}$/;
|
|
13
|
+
const serialPattern = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
14
|
+
export function validateHost(raw) {
|
|
15
|
+
const value = raw.trim();
|
|
16
|
+
if (!isIP(value) && !hostnamePattern.test(value)) {
|
|
17
|
+
throw new ValidationError('invalidHost');
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function validatePort(raw) {
|
|
22
|
+
const trimmed = raw.trim();
|
|
23
|
+
if (!/^\d+$/.test(trimmed))
|
|
24
|
+
throw new ValidationError('invalidPort');
|
|
25
|
+
const value = Number(trimmed);
|
|
26
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535) {
|
|
27
|
+
throw new ValidationError('invalidPort');
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
export function validateUsername(raw) {
|
|
32
|
+
const value = raw.trim();
|
|
33
|
+
if (!usernamePattern.test(value))
|
|
34
|
+
throw new ValidationError('invalidUsername');
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
export function validateInstallDir(raw) {
|
|
38
|
+
if ([...raw].some((character) => {
|
|
39
|
+
const code = character.codePointAt(0) ?? 0;
|
|
40
|
+
return code <= 31 || code === 127;
|
|
41
|
+
})) {
|
|
42
|
+
throw new ValidationError('invalidInstallDir');
|
|
43
|
+
}
|
|
44
|
+
const normalized = posix.normalize(raw.trim());
|
|
45
|
+
const value = normalized.length > 1 ? normalized.replace(/\/$/, '') : normalized;
|
|
46
|
+
if (!value.startsWith('/') || value === '/') {
|
|
47
|
+
throw new ValidationError('invalidInstallDir');
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
export function validateDeviceSerial(raw) {
|
|
52
|
+
const normalized = raw.trim().toLowerCase();
|
|
53
|
+
const value = normalized.startsWith('wpt-') ? normalized.slice(4) : normalized;
|
|
54
|
+
if (!serialPattern.test(value) || value.length > 32) {
|
|
55
|
+
throw new ValidationError('invalidSerial');
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
export function validateAdminPassword(raw) {
|
|
60
|
+
if ([...raw].length < 12)
|
|
61
|
+
throw new ValidationError('weakPassword');
|
|
62
|
+
return raw;
|
|
63
|
+
}
|
|
64
|
+
export function confirmAdminPassword(password, confirmation) {
|
|
65
|
+
if (password !== confirmation)
|
|
66
|
+
throw new ValidationError('passwordMismatch');
|
|
67
|
+
return password;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-wpt-iot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive local and remote installer for WPT IoT edge devices",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/chetto1983/wpt-iot.git",
|
|
8
|
+
"directory": "packages/create-wpt-iot"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-wpt-iot": "dist/bin.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.13.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node scripts/clean-dist.mjs && tsc && node scripts/stamp-installer.mjs && node scripts/verify-installer-manifest.mjs",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"verify:manifest": "node scripts/verify-installer-manifest.mjs",
|
|
27
|
+
"verify:manifest:remote": "node scripts/verify-installer-manifest.mjs --remote",
|
|
28
|
+
"prepack": "pnpm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@inquirer/prompts": "^8.7.0",
|
|
32
|
+
"execa": "^10.0.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.8.0",
|
|
37
|
+
"vitest": "^3.2.4"
|
|
38
|
+
},
|
|
39
|
+
"license": "UNLICENSED",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
43
|
+
}
|
|
44
|
+
}
|