strckro 0.2.0 → 0.3.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/DOCUMENTATION.md +18 -5
- package/PUBLISHING.md +4 -2
- package/README.md +7 -2
- package/package.json +1 -1
- package/src/cli.mjs +58 -7
package/DOCUMENTATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Strckro CLI — เอกสารการใช้งาน
|
|
2
2
|
|
|
3
|
-
เวอร์ชัน CLI: `0.
|
|
3
|
+
เวอร์ชัน CLI: `0.3.0`
|
|
4
4
|
คำสั่ง: `skctl`
|
|
5
5
|
อัปเดตเอกสาร: 6 พฤศจิกายน 2026
|
|
6
6
|
|
|
@@ -47,10 +47,10 @@ npx --package strckro skctl --help
|
|
|
47
47
|
npm run pack:release
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
ส่ง `strckro-0.
|
|
50
|
+
ส่ง `strckro-0.3.0.tgz` ไปยังเครื่องอื่นแล้วรัน:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
npm install --global ./strckro-0.
|
|
53
|
+
npm install --global ./strckro-0.3.0.tgz
|
|
54
54
|
skctl --version
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -188,15 +188,28 @@ skctl init --project my-app
|
|
|
188
188
|
|
|
189
189
|
Credential ที่บันทึกไว้จะใช้กับ server origin เดิมเท่านั้น สามารถใช้ `STRCKRO_TOKEN` สำหรับ CI โดยไม่บันทึก credential ลงเครื่อง
|
|
190
190
|
|
|
191
|
+
**token ไปได้เฉพาะ server ที่คุณเลือกเอง:** `strckro.json` เป็นไฟล์ใน repository ซึ่งอาจมาจากคนอื่น (repo ที่ clone มา, fork ใน CI, repo ที่ AI agent เปิด) CLI จึงไม่ส่ง token ที่คุณให้ (`STRCKRO_TOKEN`, `login --token`, `login --token-stdin`) ไปยัง server ที่ระบุอยู่ใน `strckro.json` เพียงอย่างเดียว Token จะถูกส่งเมื่อ server มาจาก `--server`, `STRCKRO_SERVER` หรือเป็น origin เดียวกับที่ login ไว้บนเครื่องนี้ มิฉะนั้นคำสั่งจะหยุดด้วย exit 2 โดยยังไม่ส่งคำขอใดออกไป CI ที่ใช้ `STRCKRO_TOKEN` ให้ตั้ง `STRCKRO_SERVER` คู่กันเสมอ เช่นเดียวกับตัวอย่างในหัวข้อ CI
|
|
192
|
+
|
|
191
193
|
## 6. สร้างและซิงก์โปรเจค
|
|
192
194
|
|
|
193
195
|
### ดูรายการและสถานะ
|
|
194
196
|
|
|
195
197
|
```sh
|
|
198
|
+
skctl projects list
|
|
199
|
+
skctl project my-app --info
|
|
200
|
+
skctl project --info
|
|
196
201
|
skctl projects list --json
|
|
197
|
-
skctl
|
|
202
|
+
skctl project my-app --info --json
|
|
198
203
|
```
|
|
199
204
|
|
|
205
|
+
`projects list` แสดงเฉพาะ slug, ประเภทงาน (`web`/`worker`), runtime, deployment และ process หนึ่งบรรทัดต่อโปรเจค ไม่แสดง repository, release history หรือ logs ในรายการ
|
|
206
|
+
|
|
207
|
+
`project [SLUG] --info` แสดงข้อมูลทีละโปรเจค: ชื่อ, source, branch/directory, sync, active/previous release, งาน deploy และ domains หากไม่ระบุ slug จะใช้ลำดับเลือกโปรเจคในหัวข้อ 5 ส่วน `project [SLUG]` แสดงตารางสรุปเฉพาะโปรเจคนั้น
|
|
208
|
+
|
|
209
|
+
Deployment `active` แสดงสถานะ release ที่บันทึกไว้ ส่วน process เป็นผล runtime probe แยกกัน `unknown` หมายถึงยังยืนยันสถานะ process ไม่ได้ จึงไม่ควรใช้ deployment state เพียงอย่างเดียวสรุปว่าแอป healthy
|
|
210
|
+
|
|
211
|
+
เติม `--json` เพื่อรับข้อมูล API ครบเหมือนเดิม รวม release history และ metadata ที่ token มีสิทธิ์อ่าน โดย stdout เป็น JSON object เดียว ไม่มีตารางหรือข้อความแนะนำ `skctl projects status [SLUG] --json` ยังคงใช้ได้ตามเดิม และให้โครงสร้างเดียวกับ `skctl project [SLUG] --info --json`
|
|
212
|
+
|
|
200
213
|
### สร้างหรืออัปเดตจากไฟล์
|
|
201
214
|
|
|
202
215
|
สร้าง `project.json` เช่น:
|
|
@@ -410,7 +423,7 @@ Token ไม่สามารถจัดการบัญชี/สิทธ
|
|
|
410
423
|
| Environment variable | ความหมาย |
|
|
411
424
|
| --- | --- |
|
|
412
425
|
| `STRCKRO_SERVER` | Origin ของ Portal |
|
|
413
|
-
| `STRCKRO_TOKEN` | Token สำหรับ process/CI โดยไม่ต้อง login บันทึกลงเครื่อง |
|
|
426
|
+
| `STRCKRO_TOKEN` | Token สำหรับ process/CI โดยไม่ต้อง login บันทึกลงเครื่อง ใช้คู่กับ `STRCKRO_SERVER` หรือ `--server` (ไม่ส่งไปยัง server ที่มาจาก `strckro.json` อย่างเดียว) |
|
|
414
427
|
| `STRCKRO_PROJECT` | Default project slug |
|
|
415
428
|
| `STRCKRO_CONFIG_DIR` | เปลี่ยนโฟลเดอร์เก็บ credential |
|
|
416
429
|
| `XDG_CONFIG_HOME` | Base config directory หากไม่ได้กำหนด `STRCKRO_CONFIG_DIR` |
|
package/PUBLISHING.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
Registry: `https://registry.npmjs.org/`
|
|
6
6
|
การเข้าถึง: public
|
|
7
7
|
|
|
8
|
+
รุ่น 0.3.0 เปลี่ยน `projects list` เป็นตารางสรุปหนึ่งบรรทัดต่อโปรเจค เพิ่ม `project [SLUG] --info` สำหรับเจาะรายละเอียด และคงข้อมูล `--json` กับคำสั่ง `projects status` เดิมไว้
|
|
9
|
+
|
|
8
10
|
## ตรวจแพ็กเกจก่อนเผยแพร่
|
|
9
11
|
|
|
10
12
|
```sh
|
|
@@ -53,10 +55,10 @@ skctl --version
|
|
|
53
55
|
npm run pack:release
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
จะได้ `strckro-0.
|
|
58
|
+
จะได้ `strckro-0.3.0.tgz` ตามเวอร์ชันปัจจุบัน ส่งไฟล์นี้ไปยังเครื่องอื่นแล้วติดตั้งได้ทันที:
|
|
57
59
|
|
|
58
60
|
```sh
|
|
59
|
-
npm install --global ./strckro-0.
|
|
61
|
+
npm install --global ./strckro-0.3.0.tgz
|
|
60
62
|
skctl --version
|
|
61
63
|
skctl login --server https://portal.example.com --no-browser
|
|
62
64
|
```
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npx --package strckro skctl --help
|
|
|
23
23
|
```sh
|
|
24
24
|
npm run pack:release
|
|
25
25
|
# On the other machine:
|
|
26
|
-
npm install --global ./strckro-0.
|
|
26
|
+
npm install --global ./strckro-0.3.0.tgz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
ดู [เอกสารฉบับเต็ม](DOCUMENTATION.md) และ [ขั้นตอน publish](PUBLISHING.md)
|
|
@@ -63,7 +63,8 @@ Windows เก็บ credential ด้วย DPAPI ผูกกับ OS user; L
|
|
|
63
63
|
## โปรเจคและ deploy
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
|
-
skctl projects list
|
|
66
|
+
skctl projects list
|
|
67
|
+
skctl project my-app --info
|
|
67
68
|
skctl init --project my-app
|
|
68
69
|
skctl projects status --json
|
|
69
70
|
skctl deployments list --json
|
|
@@ -76,8 +77,12 @@ skctl rollback --release RELEASE_ID
|
|
|
76
77
|
skctl jobs status JOB_ID
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
`projects list` แสดงตารางหนึ่งบรรทัดต่อโปรเจค มี slug, ประเภทงาน, runtime และสถานะ deployment/process ดูรายละเอียดด้วย `project [SLUG] --info` หรือใช้โปรเจคจาก `--project`, environment และ `strckro.json` เมื่อไม่ระบุ slug `project [SLUG]` แสดงตารางเฉพาะโปรเจคนั้น `--json` ยังคงคืนข้อมูล API ครบสำหรับ AI/CI ทั้ง list และรายละเอียด และ `projects status [SLUG]` ยังใช้ได้ตามเดิม
|
|
81
|
+
|
|
79
82
|
`init` สร้าง `strckro.json` เก็บแค่ server/project ไม่มี token ไม่เขียนทับไฟล์เดิม อาจ commit ให้ทีมใช้ร่วมกันได้ ลำดับเลือก server: `--server`, `STRCKRO_SERVER`, `strckro.json`, credential ที่บันทึก; project: `--project`, positional slug, `STRCKRO_PROJECT`, `strckro.json`
|
|
80
83
|
|
|
84
|
+
`strckro.json` เป็นไฟล์ใน repository ที่อาจมาจากคนอื่น จึงไม่ถูกใช้เป็นปลายทางของ token ที่คุณให้เอง (`STRCKRO_TOKEN`, `login --token`, `--token-stdin`): token จะส่งเฉพาะเมื่อ server มาจาก `--server`, `STRCKRO_SERVER` หรือ origin ที่ login ไว้บนเครื่องนี้ มิฉะนั้นคำสั่งหยุดด้วย exit 2 โดยไม่ส่งคำขอ ใน CI ให้ตั้ง `STRCKRO_SERVER` คู่กับ `STRCKRO_TOKEN`
|
|
85
|
+
|
|
81
86
|
สร้างโปรเจคด้วย `project.json` ตาม `POST /api/projects/sync` เช่น:
|
|
82
87
|
|
|
83
88
|
```json
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseArgs } from 'node:util';
|
|
1
|
+
import { parseArgs, stripVTControlCharacters } from 'node:util';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
@@ -21,7 +21,8 @@ Authentication:
|
|
|
21
21
|
|
|
22
22
|
Projects and deployment:
|
|
23
23
|
init --project SLUG Save server/project to ./strckro.json
|
|
24
|
-
projects list
|
|
24
|
+
projects list Compact project overview
|
|
25
|
+
project [SLUG] [--info] Overview or details for one project
|
|
25
26
|
projects status [SLUG]
|
|
26
27
|
projects sync --file project.json Create/update and sync a Git project
|
|
27
28
|
deployments list [SLUG] List deployment history and active jobs
|
|
@@ -52,6 +53,8 @@ Global options:
|
|
|
52
53
|
|
|
53
54
|
Environment: STRCKRO_SERVER, STRCKRO_TOKEN, STRCKRO_PROJECT,
|
|
54
55
|
STRCKRO_CONFIG_DIR, XDG_CONFIG_HOME
|
|
56
|
+
A token (STRCKRO_TOKEN or login --token) is sent only to --server, STRCKRO_SERVER
|
|
57
|
+
or the server of your saved login, never to one named only by ./strckro.json.
|
|
55
58
|
Legacy DASHBOARD_* variables and dpctl.json remain supported.
|
|
56
59
|
Exit codes: 0 success; 1 API/deployment; 2 input; 3 authentication;
|
|
57
60
|
4 permission denied; 5 stale environment; 6 network/timeout.
|
|
@@ -60,12 +63,43 @@ Use --json for AI/CI: stdout contains one JSON object; progress goes to stderr.
|
|
|
60
63
|
`;
|
|
61
64
|
|
|
62
65
|
const stringOptions = ['server', 'token', 'project', 'scope', 'name', 'days', 'file', 'domains', 'hostname', 'release', 'revision', 'timeout'];
|
|
63
|
-
const booleanOptions = ['json', 'help', 'version', 'no-browser', 'token-stdin', 'stdin', 'empty', 'wait', 'sync'];
|
|
66
|
+
const booleanOptions = ['json', 'help', 'version', 'no-browser', 'token-stdin', 'stdin', 'empty', 'wait', 'sync', 'info'];
|
|
64
67
|
const options = Object.fromEntries([...stringOptions.map(key => [key, { type: 'string' }]), ...booleanOptions.map(key => [key, { type: 'boolean' }])]);
|
|
65
68
|
const slug = value => { if (!/^[a-z][a-z0-9-]{0,62}$/.test(value || '')) throw new CliError('Specify a valid project slug with --project or skctl init.'); return value; };
|
|
66
69
|
const keyName = value => { if (!/^[A-Z_][A-Z0-9_]*$/.test(value || '')) throw new CliError('Specify an uppercase environment key.'); return value; };
|
|
67
70
|
const required = (value, flag) => { if (!value) throw new CliError(`Provide ${flag}.`); return value; };
|
|
68
71
|
|
|
72
|
+
// Keep each table entry on one line, even when a server field contains control characters.
|
|
73
|
+
const display = value => stripVTControlCharacters(String(value ?? '')).replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').replace(/\s+/g, ' ').trim() || '-';
|
|
74
|
+
|
|
75
|
+
function projectList(projects) {
|
|
76
|
+
if (!projects.length) return 'No projects found.\n';
|
|
77
|
+
const rows = [
|
|
78
|
+
['SLUG', 'TYPE', 'RUNTIME', 'DEPLOYMENT', 'PROCESS'],
|
|
79
|
+
...projects.map(item => [item.slug, item.serviceType || 'web', item.runtime, item.deployment?.state, item.runtimeStatus?.state || 'unknown'].map(display))
|
|
80
|
+
];
|
|
81
|
+
const widths = rows[0].map((_, index) => Math.max(...rows.map(row => row[index].length)));
|
|
82
|
+
return rows.map(row => row.map((value, index) => value.padEnd(widths[index])).join(' ').trimEnd()).join('\n')
|
|
83
|
+
+ `\n\n${projects.length} project${projects.length === 1 ? '' : 's'}. Details: skctl project <slug> --info\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function projectInfo(item) {
|
|
87
|
+
const rows = [
|
|
88
|
+
['Name', item.name], ['Slug', item.slug], ['Type', item.serviceType || 'web'], ['Runtime', item.runtime],
|
|
89
|
+
['Deployment', item.deployment?.state], ['Process', item.runtimeStatus?.state || 'unknown'],
|
|
90
|
+
['Process checked', item.runtimeStatus?.checkedAt],
|
|
91
|
+
['Active release', item.deployment?.activeReleaseId], ['Previous release', item.deployment?.previousReleaseId],
|
|
92
|
+
['Deployment updated', item.deployment?.updatedAt],
|
|
93
|
+
['Deploy job', item.deploymentProgress?.id], ['Job status', item.deploymentProgress?.status], ['Job phase', item.deploymentProgress?.phase],
|
|
94
|
+
['Repository', item.repository], ['Branch', item.branch], ['Directory', item.directory],
|
|
95
|
+
['Source sync', item.sync?.status], ['Source synced', item.sync?.at],
|
|
96
|
+
['Domains', item.domains?.hosts?.join(', ') || '(none)']
|
|
97
|
+
];
|
|
98
|
+
const width = Math.max(...rows.map(([label]) => label.length));
|
|
99
|
+
return rows.map(([label, value]) => `${label.padEnd(width)} ${display(value)}`).join('\n')
|
|
100
|
+
+ '\n\nUse --json for the complete project data.\n';
|
|
101
|
+
}
|
|
102
|
+
|
|
69
103
|
async function stdinText(input) {
|
|
70
104
|
if (input.isTTY) throw new CliError('Pipe the value into stdin.');
|
|
71
105
|
const chunks = []; let size = 0;
|
|
@@ -116,9 +150,10 @@ export async function run(argv, { env = process.env, cwd = process.cwd(), stdout
|
|
|
116
150
|
const [command, action, ...rest] = args;
|
|
117
151
|
// Logout does not need a working server or credential decryption.
|
|
118
152
|
if (command === 'logout') { if (args.length !== 1) throw new CliError('Usage: skctl logout'); await clearConfig(env); print({ ok: true, message: 'Saved credential removed. Revoke it in Portal Settings > Account to invalidate it.' }); return 0; }
|
|
119
|
-
const supported = ['login', 'whoami', 'init', 'projects', 'deployments', 'deploy', 'restart', 'rollback', 'jobs', 'logs', 'domains', 'env'];
|
|
153
|
+
const supported = ['login', 'whoami', 'init', 'project', 'projects', 'deployments', 'deploy', 'restart', 'rollback', 'jobs', 'logs', 'domains', 'env'];
|
|
120
154
|
if (!supported.includes(command)) throw new CliError('Unknown command. Run skctl --help.');
|
|
121
155
|
const commandOptions = command === 'login' ? ['token', 'token-stdin', 'no-browser', 'scope', 'name', 'days']
|
|
156
|
+
: command === 'project' ? ['info']
|
|
122
157
|
: command === 'projects' && action === 'sync' ? ['file']
|
|
123
158
|
: command === 'deploy' ? ['sync', 'wait']
|
|
124
159
|
: command === 'rollback' ? ['release']
|
|
@@ -126,10 +161,18 @@ export async function run(argv, { env = process.env, cwd = process.cwd(), stdout
|
|
|
126
161
|
: command === 'env' ? action === 'set' ? ['stdin', 'file', 'empty', 'revision'] : action === 'unset' ? ['revision'] : action === 'import' ? ['file', 'revision'] : action === 'pull' ? ['file'] : [] : [];
|
|
127
162
|
for (const flag of Object.keys(flags)) if (!['server', 'project', 'json', 'timeout', 'help', 'version', ...commandOptions].includes(flag)) throw new CliError(`--${flag} does not apply to this command.`);
|
|
128
163
|
const local = await localProject(cwd);
|
|
129
|
-
const
|
|
164
|
+
const explicitServer = flags.server || env.DASHBOARD_SERVER;
|
|
165
|
+
const requestedServer = explicitServer || local.server;
|
|
130
166
|
const needsCredential = command !== 'login' && !env.DASHBOARD_TOKEN;
|
|
131
|
-
|
|
167
|
+
// A token the person supplies (STRCKRO_TOKEN, or login --token/--token-stdin) is a bearer
|
|
168
|
+
// secret, and a login, even by browser approval, ends by replacing the credential saved on this
|
|
169
|
+
// machine. strckro.json is repository content, possibly from a stranger, so it must not pick
|
|
170
|
+
// the server for either: only --server, STRCKRO_SERVER or the origin already logged in to on
|
|
171
|
+
// this machine may be trusted with them.
|
|
172
|
+
const needsTrustedServer = command === 'login' || Boolean(env.DASHBOARD_TOKEN);
|
|
173
|
+
let saved = !requestedServer || needsCredential || (needsTrustedServer && !explicitServer) ? await readConfig(env, { secret: false }) : {};
|
|
132
174
|
const server = normalizeServer(requestedServer || saved.server);
|
|
175
|
+
if (needsTrustedServer && !explicitServer && saved.server !== server) throw new CliError(`Refusing to use ${server}: that server comes only from strckro.json, which is repository content, so no token or login is sent there. Pass --server or set STRCKRO_SERVER to the Portal you trust.`);
|
|
133
176
|
if (needsCredential && saved.server === server) saved = await readConfig(env);
|
|
134
177
|
const timeout = flags.timeout === undefined ? 30 : Number(flags.timeout);
|
|
135
178
|
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 3600) throw new CliError('Timeout must be 1–3600 seconds.');
|
|
@@ -185,6 +228,12 @@ export async function run(argv, { env = process.env, cwd = process.cwd(), stdout
|
|
|
185
228
|
if (!projects.some(item => item.slug === target)) throw new CliError('Project not found.', 'API_ERROR', 404);
|
|
186
229
|
await writeFile(resolve(cwd, 'strckro.json'), JSON.stringify({ server, project: target }, null, 2) + '\n', { flag: 'wx' });
|
|
187
230
|
result = { ok: true, server, project: target, file: resolve(cwd, 'strckro.json') };
|
|
231
|
+
} else if (command === 'project') {
|
|
232
|
+
if (args.length > 2) throw new CliError('Usage: skctl project [SLUG] [--info]');
|
|
233
|
+
const target = project(action);
|
|
234
|
+
const { projects } = await api('/api/projects');
|
|
235
|
+
result = { project: projects.find(item => item.slug === target) };
|
|
236
|
+
if (!result.project) throw new CliError('Project not found.', 'API_ERROR', 404);
|
|
188
237
|
} else if (command === 'projects') {
|
|
189
238
|
if (!['list', 'status', 'sync'].includes(action) || rest.length > (action === 'status' ? 1 : 0)) throw new CliError('Usage: skctl projects list|status [SLUG]|sync --file project.json');
|
|
190
239
|
if (action === 'sync') {
|
|
@@ -259,7 +308,9 @@ export async function run(argv, { env = process.env, cwd = process.cwd(), stdout
|
|
|
259
308
|
result = await api(endpoint, { method: 'PATCH', body: { revision, set, unset } });
|
|
260
309
|
}
|
|
261
310
|
}
|
|
262
|
-
|
|
311
|
+
if (!json && result?.ok !== false && command === 'projects' && action === 'list') stdout.write(projectList(result.projects));
|
|
312
|
+
else if (!json && result?.ok !== false && command === 'project') stdout.write(flags.info ? projectInfo(result.project) : projectList([result.project]));
|
|
313
|
+
else print({ ok: true, ...result });
|
|
263
314
|
return result?.ok === false ? 1 : 0;
|
|
264
315
|
} catch (error) {
|
|
265
316
|
const message = error instanceof CliError ? error.message : error.code === 'EEXIST' ? 'Output file already exists; choose a new path.' : error.code?.startsWith('E') ? 'Could not access the requested file or configuration.' : error.message || 'Command failed.';
|