costaff-workspace 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/dist/pull.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Fetching a published file's source back out, so it can be worked on again.
3
+ *
4
+ * The receiver decides what comes back: an owner gets the state file that makes
5
+ * the next push update the same link, anyone else gets a copy that cannot
6
+ * address it. Nothing here has to know which case it is — asking would mean two
7
+ * clients could disagree about who owns what.
8
+ */
9
+ import fs from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { PublishError, discover, formatBytes } from "./client.js";
12
+ import { loadCredential } from "./credentials.js";
13
+ async function isEmpty(dir) {
14
+ const entries = await fs.readdir(dir).catch(() => null);
15
+ return entries === null || entries.length === 0;
16
+ }
17
+ export async function pull(opts) {
18
+ const write = opts.out ?? (() => { });
19
+ const endpoint = new URL(opts.endpoint).toString().replace(/\/$/, '');
20
+ const discovery = await discover(endpoint);
21
+ if (discovery.pull === undefined) {
22
+ throw new PublishError(`${discovery.name} does not hand source back.`);
23
+ }
24
+ let bearer = opts.bearer ?? (await loadCredential(endpoint))?.token;
25
+ if (bearer === undefined) {
26
+ if (opts.signIn === undefined)
27
+ throw new PublishError('Sign in first — run with --login.');
28
+ bearer = await opts.signIn();
29
+ }
30
+ const url = `${endpoint}${discovery.pull}/${opts.token}`;
31
+ const response = await fetch(url, { headers: { authorization: `Bearer ${bearer}` } });
32
+ if (!response.ok) {
33
+ const body = (await response.json().catch(() => ({})));
34
+ throw new PublishError(body.message ?? `Pull failed (${response.status}).`, response.status);
35
+ }
36
+ const zip = new Uint8Array(await response.arrayBuffer());
37
+ const { unzipSync } = await import('fflate');
38
+ const entries = unzipSync(zip);
39
+ const target = path.resolve(opts.dir ?? opts.token);
40
+ if (opts.force !== true && !(await isEmpty(target))) {
41
+ throw new PublishError(`${target} is not empty. Pass --force to write into it anyway, or name another directory.`);
42
+ }
43
+ let bytes = 0;
44
+ let files = 0;
45
+ for (const [name, content] of Object.entries(entries)) {
46
+ if (name.endsWith('/'))
47
+ continue;
48
+ /*
49
+ * The archive comes from a server, so its names are input: a "../" here
50
+ * would write outside the directory the user named.
51
+ */
52
+ const abs = path.resolve(target, name);
53
+ if (abs !== target && !abs.startsWith(target + path.sep)) {
54
+ throw new PublishError(`Refusing an archive entry that escapes the directory: ${name}`);
55
+ }
56
+ await fs.mkdir(path.dirname(abs), { recursive: true });
57
+ await fs.writeFile(abs, content);
58
+ bytes += content.byteLength;
59
+ files += 1;
60
+ }
61
+ write(`pulled ${files} files (${formatBytes(bytes)}) into ${target}\n`);
62
+ return { dir: target, files, bytes };
63
+ }
64
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../src/pull.ts"],"names":[],"mappings":"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"}
package/dist/push.js ADDED
@@ -0,0 +1,152 @@
1
+ import { createBundle } from "./bundle.js";
2
+ import { PublishError, discover, formatBytes, pollDeviceAuth, startDeviceAuth, upload } from "./client.js";
3
+ import { clearCredential, loadCredential, saveCredential } from "./credentials.js";
4
+ import { collectSource } from "./source.js";
5
+ import { forgetToken, rememberUrl, tokenFor } from "./state.js";
6
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
7
+ /** Exported so `pull` runs the same device flow rather than its own. */
8
+ export async function login(opts, name) {
9
+ const write = opts.out ?? (() => { });
10
+ const discovery = await discover(opts.endpoint);
11
+ const start = await startDeviceAuth(opts.endpoint, discovery);
12
+ write(`\n Sign in to ${name}\n open ${start.verificationUri}\n code ${start.userCode}\n\n`);
13
+ const deadline = Date.now() + start.expiresIn * 1000;
14
+ let interval = Math.max(1, start.interval) * 1000;
15
+ while (Date.now() < deadline) {
16
+ await sleep(interval);
17
+ const result = await pollDeviceAuth(opts.endpoint, discovery, start.deviceCode);
18
+ if (result.status === 'ok') {
19
+ await saveCredential(opts.endpoint, {
20
+ token: result.token,
21
+ expiresAt: result.expiresAt,
22
+ account: result.account,
23
+ });
24
+ write(` signed in${result.account === undefined ? '' : ` as ${result.account}`}\n\n`);
25
+ return result.token;
26
+ }
27
+ if (result.status === 'slow_down') {
28
+ interval = Math.max(interval, result.interval * 1000);
29
+ continue;
30
+ }
31
+ if (result.status === 'denied')
32
+ throw new PublishError('Sign-in was denied.');
33
+ if (result.status === 'expired')
34
+ throw new PublishError('Sign-in code expired.');
35
+ }
36
+ throw new PublishError('Sign-in timed out.');
37
+ }
38
+ /**
39
+ * 取得憑證,需要的話走裝置流程。
40
+ *
41
+ * 匯出是為了讓一次推很多份的呼叫端可以先登入一次 —— 裝置授權碼是印給人看的,
42
+ * 而印它的 out 和推送進度的 out 是同一個。把進度靜音就等於把授權碼靜音,然後
43
+ * 它會安靜地輪詢到過期。先登入,就沒有那個機會。
44
+ */
45
+ export async function ensureBearer(opts) {
46
+ const endpoint = new URL(opts.endpoint).toString();
47
+ return resolveBearer({ ...opts, endpoint, slug: '', kind: 'document', title: '', route: '/', siteDir: '' }, (await discover(endpoint)).name);
48
+ }
49
+ async function resolveBearer(opts, name) {
50
+ if (opts.bearer !== undefined)
51
+ return opts.bearer;
52
+ const fromEnv = process.env.COSTAFF_WORKSPACE_TOKEN?.trim();
53
+ if (fromEnv)
54
+ return fromEnv;
55
+ if (opts.login !== true) {
56
+ const stored = await loadCredential(opts.endpoint);
57
+ if (stored !== null)
58
+ return stored.token;
59
+ }
60
+ return login(opts, name);
61
+ }
62
+ export async function push(opts) {
63
+ const write = opts.out ?? (() => { });
64
+ const cwd = opts.cwd ?? process.cwd();
65
+ const endpoint = new URL(opts.endpoint).toString();
66
+ if (opts.logout === true) {
67
+ await clearCredential(endpoint);
68
+ write(`signed out of ${endpoint}\n`);
69
+ return null;
70
+ }
71
+ // Signing in is useful on its own — a scripted push runs non-interactively
72
+ // and cannot show a device code, so the login is done once up front.
73
+ if (opts.login === true && opts.slug === '') {
74
+ await resolveBearer(opts, (await discover(endpoint)).name);
75
+ return null;
76
+ }
77
+ // The token is settled before anything is packed: the bundle's asset paths
78
+ // were baked against it at build time, so discovering it late would mean
79
+ // discovering it wrong.
80
+ const token = opts.token ?? (await tokenFor(cwd, endpoint, opts.kind, opts.slug));
81
+ const source = opts.sourceRoot === undefined || opts.sourceDir === undefined
82
+ ? undefined
83
+ : await collectSource({
84
+ root: opts.sourceRoot,
85
+ dir: opts.sourceDir,
86
+ entry: opts.sourceEntry ?? 'index.tsx',
87
+ });
88
+ const bundle = await createBundle({
89
+ source,
90
+ siteDir: opts.siteDir,
91
+ pushedAt: new Date().toISOString(),
92
+ manifest: {
93
+ slug: opts.slug,
94
+ kind: opts.kind,
95
+ token,
96
+ title: opts.title,
97
+ subtitle: opts.subtitle,
98
+ folder: opts.folder,
99
+ route: opts.route,
100
+ entry: opts.entry ?? 'index.html',
101
+ },
102
+ });
103
+ const summary = `${bundle.manifest.entry} + ${formatBytes(bundle.bytes.byteLength)}` +
104
+ (source === undefined ? '' : `, source included (${source.files.size} files)`);
105
+ /*
106
+ * 乾跑到此為止,而且刻意在 discover 之前 —— 它的工作是打包並回報,那件事不需要
107
+ * 網路。要它先連上伺服器,等於在飛機上、或伺服器還沒上線時就不能檢查自己的設定。
108
+ */
109
+ if (opts.dryRun === true) {
110
+ write(`would push ${opts.slug} to ${endpoint}\n ${summary}\n`);
111
+ return null;
112
+ }
113
+ const discovery = await discover(endpoint);
114
+ let bearer = await resolveBearer(opts, discovery.name);
115
+ let result;
116
+ try {
117
+ result = await upload(endpoint, discovery, bearer, bundle.bytes);
118
+ }
119
+ catch (err) {
120
+ /*
121
+ * Push credentials expire, and the receiver may end one early from the
122
+ * devices page. Either way the stored copy is worthless, so it is dropped
123
+ * and the device flow runs again rather than telling someone to pass a flag
124
+ * they would only ever answer with "yes".
125
+ */
126
+ if (err instanceof PublishError && (err.status === 401 || err.status === 403)) {
127
+ await clearCredential(endpoint);
128
+ bearer = await login(opts, discovery.name);
129
+ result = await upload(endpoint, discovery, bearer, bundle.bytes);
130
+ await rememberUrl(cwd, endpoint, opts.kind, opts.slug, token, result.url);
131
+ write(`${result.updated ? 'updated' : 'pushed'} ${result.workspace}/${opts.slug}\n ${result.url}\n`);
132
+ return result;
133
+ }
134
+ // The remembered address is spoken for by another file, so it can never
135
+ // work for this one. Forgetting it lets the next run mint a fresh token —
136
+ // the bundle cannot be re-baked here, since its asset paths carry the old
137
+ // one, so recovery has to be the next build rather than a retry.
138
+ if (err instanceof PublishError && err.code === 'token_taken') {
139
+ const forgot = await forgetToken(cwd, endpoint, opts.kind, opts.slug, token);
140
+ if (forgot) {
141
+ throw new PublishError(`${err.message} Forgot it — build and push again.`, err.status);
142
+ }
143
+ }
144
+ throw err;
145
+ }
146
+ await rememberUrl(cwd, endpoint, opts.kind, opts.slug, token, result.url);
147
+ write(`${result.updated ? 'updated' : 'pushed'} ${result.workspace}/${opts.slug}\n ${result.url}\n`);
148
+ if (!result.updated)
149
+ write(' 只有你看得到 — 在網頁上開這份檔案,用「分享」決定誰能看\n');
150
+ return result;
151
+ }
152
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"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"}
package/dist/source.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Collecting what a rebuild needs, so a pushed file can be worked on again.
3
+ *
4
+ * The receiver only ever held build output: 360 KB of hashed assets for an 8 KB
5
+ * `index.tsx`. Nothing in that is editable, so "download the file" could not
6
+ * mean "edit the file" until the source travelled with it.
7
+ *
8
+ * What travels is the item's own directory plus the workspace scaffolding that
9
+ * built it — not a curated subset. A guessed subset is a rebuild that fails on
10
+ * someone else's machine for a reason neither of us can see.
11
+ */
12
+ import fs from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ /** Workspace-level files a single-item rebuild needs, when they exist. */
15
+ const SCAFFOLD = [
16
+ 'open-doc.config.ts',
17
+ 'open-slide.config.ts',
18
+ 'open-sheet.config.ts',
19
+ 'pnpm-workspace.yaml',
20
+ 'scripts',
21
+ ];
22
+ /** Local specs name a path on the publisher's disk and mean nothing elsewhere. */
23
+ const LOCAL_SPEC = /^(link:|file:|workspace:|portal:)/;
24
+ async function collect(dir, rel) {
25
+ const out = new Map();
26
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => null);
27
+ if (entries === null)
28
+ return out;
29
+ for (const entry of entries) {
30
+ if (entry.name === 'node_modules' || entry.name.startsWith('.'))
31
+ continue;
32
+ const abs = path.join(dir, entry.name);
33
+ const key = `${rel}/${entry.name}`;
34
+ if (entry.isDirectory()) {
35
+ for (const [k, v] of await collect(abs, key))
36
+ out.set(k, v);
37
+ }
38
+ else if (entry.isFile()) {
39
+ out.set(key, new Uint8Array(await fs.readFile(abs)));
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * Rewrites a local dependency to the version actually installed.
46
+ *
47
+ * `link:../../open-doc/packages/core` is how the author develops the framework
48
+ * and their own documents side by side. Shipped as-is it is a path that does
49
+ * not exist for anyone else, so the rebuild dies at install with an error about
50
+ * a directory rather than about the dependency.
51
+ */
52
+ async function resolveSpecs(root, specs) {
53
+ const out = {};
54
+ for (const [name, spec] of Object.entries(specs)) {
55
+ if (!LOCAL_SPEC.test(spec)) {
56
+ out[name] = spec;
57
+ continue;
58
+ }
59
+ const installed = path.join(root, 'node_modules', name, 'package.json');
60
+ const raw = await fs.readFile(installed, 'utf8').catch(() => null);
61
+ if (raw === null) {
62
+ throw new Error(`${name} is "${spec}" but is not installed, so there is no version to publish it as. ` +
63
+ 'Install the workspace before pushing.');
64
+ }
65
+ const version = JSON.parse(raw).version;
66
+ if (typeof version !== 'string') {
67
+ throw new Error(`${name} has no version in its package.json.`);
68
+ }
69
+ out[name] = `^${version}`;
70
+ }
71
+ return out;
72
+ }
73
+ export async function collectSource(input) {
74
+ const itemDir = path.join(input.root, input.dir);
75
+ const files = await collect(itemDir, 'item');
76
+ if (files.size === 0) {
77
+ throw new Error(`No source at ${itemDir} — nothing to publish for editing.`);
78
+ }
79
+ if (!files.has(`item/${input.entry}`)) {
80
+ throw new Error(`${itemDir} has no ${input.entry}.`);
81
+ }
82
+ for (const name of SCAFFOLD) {
83
+ const abs = path.join(input.root, name);
84
+ const stat = await fs.stat(abs).catch(() => null);
85
+ if (stat === null)
86
+ continue;
87
+ if (stat.isDirectory()) {
88
+ for (const [k, v] of await collect(abs, `scaffold/${name}`))
89
+ files.set(k, v);
90
+ }
91
+ else {
92
+ files.set(`scaffold/${name}`, new Uint8Array(await fs.readFile(abs)));
93
+ }
94
+ }
95
+ const pkgRaw = await fs.readFile(path.join(input.root, 'package.json'), 'utf8').catch(() => null);
96
+ const pkg = pkgRaw === null
97
+ ? {}
98
+ : JSON.parse(pkgRaw);
99
+ return {
100
+ dir: input.dir,
101
+ entry: input.entry,
102
+ dependencies: await resolveSpecs(input.root, pkg.dependencies ?? {}),
103
+ devDependencies: await resolveSpecs(input.root, pkg.devDependencies ?? {}),
104
+ files,
105
+ };
106
+ }
107
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,0EAA0E;AAC1E,MAAM,QAAQ,GAAG;IACf,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;IACrB,SAAS;CACV,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,GAAG,mCAAmC,CAAC;AAoBvD,KAAK,UAAU,OAAO,CAAC,GAAW,EAAE,GAAW;IAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,GAAG,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,YAAY,CACzB,IAAY,EACZ,KAA6B;IAE7B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YACjB,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,QAAQ,IAAI,mEAAmE;gBACpF,uCAAuC,CAC1C,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC,OAAO,CAAC;QAClE,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,sCAAsC,CAAC,CAAC;QACjE,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAkB;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,gBAAgB,OAAO,oCAAoC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,WAAW,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAC5B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,GAAG,EAAE,YAAY,IAAI,EAAE,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,EAAE,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClG,MAAM,GAAG,GACP,MAAM,KAAK,IAAI;QACb,CAAC,CAAC,EAAE;QACJ,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAGhB,CAAC;IAET,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,YAAY,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;QACpE,eAAe,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;QAC1E,KAAK;KACN,CAAC;AACJ,CAAC"}
package/dist/stage.js ADDED
@@ -0,0 +1,50 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ /**
5
+ * Builds one item into a bundle of its own.
6
+ *
7
+ * Per-item isolation cannot be enforced inside a shared bundle: every document
8
+ * in a `docs/` build shares the same asset directory, so anyone holding a link
9
+ * to one can fetch the chunks of the others. The only way a share can actually
10
+ * withhold the rest of a workspace is for the rest of the workspace not to be
11
+ * in the bundle — hence one staged project, and one build, per item.
12
+ *
13
+ * The cost is real: N builds instead of one, and assets repeated across them.
14
+ */
15
+ export async function buildItem({ root, surface, id, outDir, config, }) {
16
+ const stage = path.join(root, '.build', 'stage', id);
17
+ await fs.rm(stage, { recursive: true, force: true });
18
+ await fs.mkdir(path.join(stage, surface.dir), { recursive: true });
19
+ await fs.cp(path.join(root, surface.dir, id), path.join(stage, surface.dir, id), {
20
+ recursive: true,
21
+ });
22
+ // Shared inputs a single item may still reference.
23
+ for (const shared of ['themes', 'assets']) {
24
+ const from = path.join(root, shared);
25
+ if (await exists(from))
26
+ await fs.cp(from, path.join(stage, shared), { recursive: true });
27
+ }
28
+ // The staged project resolves the same packages as the workspace, so a build
29
+ // here is the same build the workspace would have produced.
30
+ await fs.symlink(path.join(root, 'node_modules'), path.join(stage, 'node_modules'), 'dir');
31
+ await fs.writeFile(path.join(stage, 'package.json'), `${JSON.stringify({ name: `stage-${id}`, private: true, type: 'module' }, null, 2)}\n`);
32
+ if (config !== undefined) {
33
+ await fs.writeFile(path.join(stage, config.file), config.body);
34
+ }
35
+ execFileSync('npx', [surface.cli, ...surface.buildArgs(path.resolve(outDir))], {
36
+ cwd: stage,
37
+ stdio: ['ignore', 'pipe', 'pipe'],
38
+ });
39
+ await fs.rm(stage, { recursive: true, force: true });
40
+ }
41
+ async function exists(p) {
42
+ try {
43
+ await fs.stat(p);
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ //# sourceMappingURL=stage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage.js","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"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"}
package/dist/state.js ADDED
@@ -0,0 +1,101 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ /**
5
+ * Remembers each file's public address.
6
+ *
7
+ * The token is generated here, before the build, because the bundle's asset
8
+ * paths are baked against it — and it is remembered so a second push updates
9
+ * the same link instead of scattering a new one. It holds no secret: committing
10
+ * this file is the supported way for a team to keep one set of links.
11
+ */
12
+ export function statePath(userCwd) {
13
+ return path.join(userCwd, '.costaff-workspace', 'files.json');
14
+ }
15
+ const originOf = (endpoint) => new URL(endpoint).origin;
16
+ /**
17
+ * Keyed by kind and slug within an endpoint.
18
+ *
19
+ * It used to carry a workspace name too, because one project could be pushed
20
+ * into two of them and a shared token would collide. An account now has exactly
21
+ * one workspace, decided by the receiver from the credentials — so within an
22
+ * endpoint there is nothing left for a workspace segment to disambiguate.
23
+ */
24
+ const keyOf = (kind, slug) => `${kind}/${slug}`;
25
+ async function read(file) {
26
+ try {
27
+ const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
28
+ if (typeof parsed.endpoints === 'object' && parsed.endpoints !== null)
29
+ return parsed;
30
+ }
31
+ catch {
32
+ // No state yet, or it was hand-edited into something unreadable.
33
+ }
34
+ return { version: 1, endpoints: {} };
35
+ }
36
+ /** Lowercase alphanumerics only: the token is a path segment in every link. */
37
+ export function newToken() {
38
+ return randomBytes(16).toString('base64url').replace(/[^a-z0-9]/gi, '').toLowerCase().slice(0, 20);
39
+ }
40
+ export async function tokenFor(userCwd, endpoint, kind, slug) {
41
+ const file = statePath(userCwd);
42
+ const state = await read(file);
43
+ const origin = originOf(endpoint);
44
+ const key = keyOf(kind, slug);
45
+ const forOrigin = state.endpoints[origin] ?? {};
46
+ const existing = forOrigin[key]?.token;
47
+ if (existing !== undefined)
48
+ return existing;
49
+ /*
50
+ * An entry written when the key carried a workspace still points at a live
51
+ * link, so it is adopted rather than replaced — the first one found wins and
52
+ * the old key is dropped. Losing it would mint a new token and quietly orphan
53
+ * whatever had already been shared.
54
+ */
55
+ const rest = {};
56
+ let adopted;
57
+ const suffix = `/${kind}/${slug}`;
58
+ for (const [k, v] of Object.entries(forOrigin)) {
59
+ if (adopted === undefined && k.endsWith(suffix) && k !== key)
60
+ adopted = v;
61
+ else
62
+ rest[k] = v;
63
+ }
64
+ const entry = adopted ?? { token: newToken() };
65
+ state.endpoints[origin] = { ...rest, [key]: entry };
66
+ await fs.mkdir(path.dirname(file), { recursive: true });
67
+ await fs.writeFile(file, `${JSON.stringify(state, null, 2)}\n`);
68
+ return entry.token;
69
+ }
70
+ export async function rememberUrl(userCwd, endpoint, kind, slug, token, url) {
71
+ const file = statePath(userCwd);
72
+ const state = await read(file);
73
+ const origin = originOf(endpoint);
74
+ state.endpoints[origin] = {
75
+ ...state.endpoints[origin],
76
+ [keyOf(kind, slug)]: { token, url },
77
+ };
78
+ await fs.mkdir(path.dirname(file), { recursive: true });
79
+ await fs.writeFile(file, `${JSON.stringify(state, null, 2)}\n`);
80
+ }
81
+ /**
82
+ * Drops a remembered token the receiver has refused, so the next run mints a
83
+ * fresh one. Only the entry that actually holds `token` is removed: a caller
84
+ * may pass an address of its own, and that must not clear an unrelated one.
85
+ */
86
+ export async function forgetToken(userCwd, endpoint, kind, slug, token) {
87
+ const file = statePath(userCwd);
88
+ const state = await read(file);
89
+ const origin = originOf(endpoint);
90
+ const forOrigin = state.endpoints[origin];
91
+ if (forOrigin === undefined)
92
+ return false;
93
+ const key = keyOf(kind, slug);
94
+ if (forOrigin[key]?.token !== token)
95
+ return false;
96
+ const { [key]: _dropped, ...rest } = forOrigin;
97
+ state.endpoints[origin] = rest;
98
+ await fs.writeFile(file, `${JSON.stringify(state, null, 2)}\n`);
99
+ return true;
100
+ }
101
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "costaff-workspace",
3
+ "version": "0.1.0",
4
+ "description": "Publish documents, decks and workbooks to CoStaff Workspace, and pull their source back out",
5
+ "keywords": [
6
+ "costaff",
7
+ "open-doc",
8
+ "open-slide",
9
+ "open-sheet",
10
+ "publish",
11
+ "cli",
12
+ "documents"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "Simon Liu",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/simonliu-ai-product/costaff-workspace-oss.git",
19
+ "directory": "cli"
20
+ },
21
+ "homepage": "https://github.com/simonliu-ai-product/costaff-workspace-oss#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/simonliu-ai-product/costaff-workspace-oss/issues"
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "type": "module",
29
+ "bin": {
30
+ "costaff-workspace": "dist/main.js"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
37
+ "prepare": "pnpm build",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "push": "tsx src/main.ts push"
41
+ },
42
+ "dependencies": {
43
+ "fflate": "^0.8.3"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.0.0",
47
+ "tsx": "^4.19.2",
48
+ "typescript": "^5.7.2",
49
+ "vitest": "^4.0.0"
50
+ }
51
+ }