@krapwoo/ds-viewer 0.4.0 → 0.4.2
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 +11 -0
- package/dist/cli/dev.js +5 -2
- package/dist/cli/init.js +3 -3
- package/dist/cli/packageManager.js +11 -0
- package/dist/cli/preflight.js +9 -3
- package/dist/cli/update.js +7 -4
- package/dist/cli/updatePlan.js +12 -3
- package/package.json +1 -1
- package/starter-kit/components/Banner/Banner.tsx +6 -1
- package/starter-kit/components/BottomSheet/BottomSheet.tsx +7 -2
- package/starter-kit/components/Dialog/Dialog.tsx +7 -2
package/README.md
CHANGED
|
@@ -355,6 +355,17 @@ Set `starterKit.root` in `ds-viewer.config.ts` if you moved the kit after `init
|
|
|
355
355
|
(default `src/ds`); a config from before 0.4 with no `root` field still works — it's inferred from
|
|
356
356
|
your first `components` glob.
|
|
357
357
|
|
|
358
|
+
### Updating: known limits
|
|
359
|
+
|
|
360
|
+
- **Updating needs the network.** If npm doesn't answer within 20 seconds, the update page shows
|
|
361
|
+
"Couldn’t prepare the update" and nothing changes. It doesn't fall back to npm's cache, because
|
|
362
|
+
the install that follows would need the network anyway.
|
|
363
|
+
- **A console error while installing is expected.** While npm replaces the package, Metro briefly
|
|
364
|
+
reports that it can't resolve `@krapwoo/ds-viewer`. The update page keeps going, and the viewer
|
|
365
|
+
restarts on the new version.
|
|
366
|
+
- **Windows hasn't been tested on a real machine yet.** Package-manager commands run through a
|
|
367
|
+
shell on Windows (needed for `npm.cmd`), and this is covered by unit tests only.
|
|
368
|
+
|
|
358
369
|
## What's included
|
|
359
370
|
|
|
360
371
|
Tokens · Icons (45) · and generic components:
|
package/dist/cli/dev.js
CHANGED
|
@@ -14,7 +14,7 @@ import { readOwnVersion } from "./packageVersion.js";
|
|
|
14
14
|
import { createUpdateEndpoint } from "./endpoint.js";
|
|
15
15
|
import { buildUpdatePlan } from "./updatePlan.js";
|
|
16
16
|
import { lastSummaryLine } from "./doctor.js";
|
|
17
|
-
import { detectPackageManager, installUpgradeCommand } from "./packageManager.js";
|
|
17
|
+
import { detectPackageManager, installUpgradeCommand, platformCommand } from "./packageManager.js";
|
|
18
18
|
export class LocalInstallMissingError extends Error {
|
|
19
19
|
}
|
|
20
20
|
const IGNORED_WATCH_SEGMENTS = new Set(['.ds-viewer', 'node_modules', '.git']);
|
|
@@ -80,7 +80,10 @@ export async function refreshUpdateFile(config, ownVersion, options = {}) {
|
|
|
80
80
|
// `/update/status` polls throughout (Critical finding, Fable correction pass: the synchronous
|
|
81
81
|
// version blocked the entire event loop until the whole update finished).
|
|
82
82
|
const execFileAsync = promisify(execFile);
|
|
83
|
-
const defaultExecImpl = async (command, args, options) =>
|
|
83
|
+
const defaultExecImpl = async (command, args, options) => {
|
|
84
|
+
const run = platformCommand(command, args);
|
|
85
|
+
return (await execFileAsync(run.command, run.args, { ...options, shell: run.shell })).stdout;
|
|
86
|
+
};
|
|
84
87
|
function installedMainJs(projectRoot) {
|
|
85
88
|
return path.join(projectRoot, 'node_modules', '@krapwoo', 'ds-viewer', 'dist', 'cli', 'main.js');
|
|
86
89
|
}
|
package/dist/cli/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFi
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createInterface } from 'node:readline/promises';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import { preflight } from "./preflight.js";
|
|
5
|
+
import { formatPreflightIssue, preflight } from "./preflight.js";
|
|
6
6
|
import { detectComponentFolders, detectTokenFiles } from "./detect.js";
|
|
7
7
|
import { ensureAgentsFileSection } from "./agentsFile.js";
|
|
8
8
|
import { ensureGithubAction } from "./githubAction.js";
|
|
@@ -51,7 +51,7 @@ function detectIndent(raw) {
|
|
|
51
51
|
export async function initExistingProject(projectRoot, options = {}) {
|
|
52
52
|
const { errors, warnings } = preflight(projectRoot);
|
|
53
53
|
if (errors.length > 0) {
|
|
54
|
-
return { messages: errors.map(
|
|
54
|
+
return { messages: errors.map(formatPreflightIssue), written: [], exitCode: 1 };
|
|
55
55
|
}
|
|
56
56
|
const componentFolders = detectComponentFolders(projectRoot, projectRoot);
|
|
57
57
|
const tokenFiles = detectTokenFiles(projectRoot, projectRoot, (file) => readFileSync(file, 'utf8'));
|
|
@@ -218,7 +218,7 @@ export default defineConfig({
|
|
|
218
218
|
export async function initNewProject(projectRoot, options = {}) {
|
|
219
219
|
const { errors, warnings } = preflight(projectRoot);
|
|
220
220
|
if (errors.length > 0) {
|
|
221
|
-
return { messages: errors.map(
|
|
221
|
+
return { messages: errors.map(formatPreflightIssue), written: [], exitCode: 1 };
|
|
222
222
|
}
|
|
223
223
|
const packageJson = JSON.parse(readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
224
224
|
const installedPackages = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
@@ -27,3 +27,14 @@ export function installUpgradeCommand(pm, pkg, version) {
|
|
|
27
27
|
return { command: 'pnpm', args: ['add', '--save-dev', spec] };
|
|
28
28
|
return { command: 'yarn', args: ['add', '--dev', spec] };
|
|
29
29
|
}
|
|
30
|
+
const WINDOWS_SHIMS = new Set(['npm', 'npx', 'pnpm', 'yarn']);
|
|
31
|
+
/** On Windows, `npm`, `npx`, `pnpm` and `yarn` are `.cmd` shims, which Node refuses to run
|
|
32
|
+
* without a shell (the CVE-2024-27980 fix). With a shell, Node joins the arguments with spaces,
|
|
33
|
+
* so any argument containing a space or a shell metacharacter (a temp folder under
|
|
34
|
+
* `C:\Users\Jane Doe\…`) is wrapped in double quotes. Real executables (`node`, `tar`, `git`)
|
|
35
|
+
* and every other platform keep running without a shell. */
|
|
36
|
+
export function platformCommand(command, args, platform = process.platform) {
|
|
37
|
+
if (platform !== 'win32' || !WINDOWS_SHIMS.has(command))
|
|
38
|
+
return { command, args, shell: false };
|
|
39
|
+
return { command, args: args.map((arg) => (/[\s"&|<>^()]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg)), shell: true };
|
|
40
|
+
}
|
package/dist/cli/preflight.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
/** One line per issue: the problem (or "Missing <package>.") and the command that fixes it. */
|
|
4
|
+
export function formatPreflightIssue(issue) {
|
|
5
|
+
return `${issue.problem ?? `Missing ${issue.package}.`} Run: ${issue.installCommand}`;
|
|
6
|
+
}
|
|
3
7
|
const REQUIRED_PACKAGES = ['react-native-web', 'react-dom'];
|
|
4
|
-
// @expo/metro-runtime is recommended, not required: Expo
|
|
8
|
+
// @expo/metro-runtime is recommended, not required: Expo web works without it (verified in the
|
|
5
9
|
// 0.1 spike and in native-preview's own node_modules, which has no @expo/metro-runtime installed
|
|
6
10
|
// either) — design §1 "Host requirements" only warns when it is missing.
|
|
7
11
|
const RECOMMENDED_PACKAGES = {
|
|
@@ -9,7 +13,9 @@ const RECOMMENDED_PACKAGES = {
|
|
|
9
13
|
typescript: 'npm install --save-dev typescript',
|
|
10
14
|
};
|
|
11
15
|
const MIN_NODE = [20, 19, 0];
|
|
12
|
-
|
|
16
|
+
// Lowered from 57 in 0.4.2 (owner-approved): sync, doctor, the viewer and Update now were verified
|
|
17
|
+
// end to end on a fresh Expo SDK 54 app and on Skiffr (Expo 54).
|
|
18
|
+
const MIN_EXPO = [54, 0, 0];
|
|
13
19
|
/** Accepts `x.y.z`, and a version with a missing minor/patch (e.g. `"57"`, `"^57"`) by defaulting
|
|
14
20
|
* the missing segments to 0 — a range specifier like `"^57"` must not be flagged as older than 57. */
|
|
15
21
|
function parseVersion(version) {
|
|
@@ -46,7 +52,7 @@ export function preflight(projectRoot) {
|
|
|
46
52
|
errors.push({ package: 'expo', installCommand: 'npx expo install expo' });
|
|
47
53
|
}
|
|
48
54
|
else if (!atLeast(expoVersion, MIN_EXPO)) {
|
|
49
|
-
errors.push({ package: 'expo',
|
|
55
|
+
errors.push({ package: 'expo', problem: `Expo SDK 54 or later is required (found ${expoVersion}).`, installCommand: 'npx expo install expo@^54' });
|
|
50
56
|
}
|
|
51
57
|
for (const name of REQUIRED_PACKAGES) {
|
|
52
58
|
if (!deps[name])
|
package/dist/cli/update.js
CHANGED
|
@@ -3,8 +3,8 @@ import { execFileSync } from 'node:child_process';
|
|
|
3
3
|
import { createInterface } from 'node:readline/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { buildUpdatePlan } from "./updatePlan.js";
|
|
7
|
-
import { detectPackageManager, installUpgradeCommand } from "./packageManager.js";
|
|
6
|
+
import { buildUpdatePlan, isAlreadyUpToDate } from "./updatePlan.js";
|
|
7
|
+
import { detectPackageManager, installUpgradeCommand, platformCommand } from "./packageManager.js";
|
|
8
8
|
import { readOwnVersion } from "./packageVersion.js";
|
|
9
9
|
import { lastSummaryLine } from "./doctor.js";
|
|
10
10
|
function formatPlanHuman(plan) {
|
|
@@ -37,7 +37,7 @@ export async function runUpdate(config, options = {}) {
|
|
|
37
37
|
const currentVersion = options.currentVersion ?? readOwnVersion(path.dirname(fileURLToPath(import.meta.url)));
|
|
38
38
|
const plan = await (options.buildPlan ?? buildUpdatePlan)(config, currentVersion);
|
|
39
39
|
if ('error' in plan)
|
|
40
|
-
return { output: plan.error, exitCode: 1 };
|
|
40
|
+
return { output: plan.error, exitCode: isAlreadyUpToDate(plan.error) ? 0 : 1 };
|
|
41
41
|
const planText = formatPlanHuman(plan);
|
|
42
42
|
if (options.dryRun)
|
|
43
43
|
return { output: planText, exitCode: 0 };
|
|
@@ -55,7 +55,10 @@ export async function runUpdate(config, options = {}) {
|
|
|
55
55
|
if (!proceed)
|
|
56
56
|
return { output: 'Aborted: nothing was changed.', exitCode: 0 };
|
|
57
57
|
}
|
|
58
|
-
const execImpl = options.execImpl ?? ((cmd, args, opts) =>
|
|
58
|
+
const execImpl = options.execImpl ?? ((cmd, args, opts) => {
|
|
59
|
+
const run = platformCommand(cmd, args);
|
|
60
|
+
return execFileSync(run.command, run.args, { ...opts, encoding: 'utf8', shell: run.shell });
|
|
61
|
+
});
|
|
59
62
|
const { command, args } = installUpgradeCommand(detectPackageManager(config.projectRoot), '@krapwoo/ds-viewer', plan.latest);
|
|
60
63
|
try {
|
|
61
64
|
execImpl(command, args, { cwd: config.projectRoot, encoding: 'utf8' });
|
package/dist/cli/updatePlan.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdtempSync, rmSync } from 'node:fs';
|
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
|
-
import { detectPackageManager } from "./packageManager.js";
|
|
7
|
+
import { detectPackageManager, platformCommand } from "./packageManager.js";
|
|
8
8
|
import { listKitFileDiffs } from "./kitDiff.js";
|
|
9
9
|
import { extractSummaryBullets, fetchWithTimeout } from "./updateCheck.js";
|
|
10
10
|
import { compareVersions, isBreakingUpgrade, parseVersion } from "./semver.js";
|
|
@@ -88,12 +88,21 @@ function defaultGitStatus(projectRoot, plannedPaths) {
|
|
|
88
88
|
throw new GitStatusError(`git status failed: ${error.message}`);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
/** "Already on the latest version" is reported through the plan's `{ error }` channel, but it is not a
|
|
92
|
+
* failure: `update` exits 0 for it, and the viewer shows it as up to date (`isAlreadyUpToDateError`). */
|
|
93
|
+
export const ALREADY_UP_TO_DATE = 'Already on the latest version';
|
|
94
|
+
export function isAlreadyUpToDate(error) {
|
|
95
|
+
return error.startsWith(ALREADY_UP_TO_DATE);
|
|
96
|
+
}
|
|
91
97
|
/** Design §5 "Update plan", steps 1-3, in full. Every external effect is injected (Global
|
|
92
98
|
* Constraints) — the real defaults are a real `npm view`/`npm pack`/`tar`/`node migrate` and a
|
|
93
99
|
* real `git status --porcelain`, exactly what Task 20's own Verdaccio spike already exercised. */
|
|
94
100
|
export async function buildUpdatePlan(config, currentVersion, options = {}) {
|
|
95
101
|
const execFileAsync = promisify(execFile);
|
|
96
|
-
const execImpl = options.execImpl ?? (async (cmd, args, opts) =>
|
|
102
|
+
const execImpl = options.execImpl ?? (async (cmd, args, opts) => {
|
|
103
|
+
const run = platformCommand(cmd, args);
|
|
104
|
+
return (await execFileAsync(run.command, run.args, { ...opts, shell: run.shell })).stdout;
|
|
105
|
+
});
|
|
97
106
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
98
107
|
const tmpDirImpl = options.tmpDirImpl ?? (() => mkdtempSync(path.join(tmpdir(), 'ds-viewer-update-')));
|
|
99
108
|
const gitStatusImpl = options.gitStatusImpl ?? defaultGitStatus;
|
|
@@ -114,7 +123,7 @@ export async function buildUpdatePlan(config, currentVersion, options = {}) {
|
|
|
114
123
|
// installed version ahead of the registry's own `npm view` result (e.g. a local prerelease) is
|
|
115
124
|
// not something `update` should try to "downgrade" into.
|
|
116
125
|
if (compareVersions(parseVersion(targetVersion), parseVersion(currentVersion)) <= 0) {
|
|
117
|
-
return { error:
|
|
126
|
+
return { error: `${ALREADY_UP_TO_DATE} (${currentVersion}).` };
|
|
118
127
|
}
|
|
119
128
|
let migrateResult;
|
|
120
129
|
// M4 (Minor, Fable correction pass): compared against the *downloaded target version's* own
|
package/package.json
CHANGED
|
@@ -173,6 +173,11 @@ export function Banner({
|
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/** `StyleSheet.absoluteFill`, spelled out: React Native 0.81's types don't allow spreading
|
|
177
|
+
* `StyleSheet.absoluteFill` (and 0.86 removed `absoluteFillObject`), so this one form
|
|
178
|
+
* type-checks on both. */
|
|
179
|
+
const FILL = { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 } as const;
|
|
180
|
+
|
|
176
181
|
const styles = StyleSheet.create({
|
|
177
182
|
actionRow: {
|
|
178
183
|
flexDirection: 'row',
|
|
@@ -235,7 +240,7 @@ const styles = StyleSheet.create({
|
|
|
235
240
|
},
|
|
236
241
|
// Translucent pressed overlay — sits above the banner bg so the tinted bg shows through.
|
|
237
242
|
pressOverlay: {
|
|
238
|
-
...
|
|
243
|
+
...FILL,
|
|
239
244
|
backgroundColor: DS_SEMANTIC.interaction.pressed,
|
|
240
245
|
borderRadius: DS_RADIUS.medium,
|
|
241
246
|
},
|
|
@@ -125,9 +125,14 @@ export function BottomSheet({ visible, onDismiss, header, children, footer, styl
|
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/** `StyleSheet.absoluteFill`, spelled out: React Native 0.81's types don't allow spreading
|
|
129
|
+
* `StyleSheet.absoluteFill` (and 0.86 removed `absoluteFillObject`), so this one form
|
|
130
|
+
* type-checks on both. */
|
|
131
|
+
const FILL = { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 } as const;
|
|
132
|
+
|
|
128
133
|
const styles = StyleSheet.create({
|
|
129
|
-
overlay: { ...
|
|
130
|
-
backdrop: { ...
|
|
134
|
+
overlay: { ...FILL, justifyContent: 'flex-end' },
|
|
135
|
+
backdrop: { ...FILL, backgroundColor: DS_SEMANTIC.element.overlayBackdrop },
|
|
131
136
|
sheet: {
|
|
132
137
|
maxHeight: MAX_HEIGHT_PERCENT,
|
|
133
138
|
backgroundColor: DS_SEMANTIC.surface.white,
|
|
@@ -63,14 +63,19 @@ export function Dialog({ visible, onDismiss, children, style }: DialogProps) {
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** `StyleSheet.absoluteFill`, spelled out: React Native 0.81's types don't allow spreading
|
|
67
|
+
* `StyleSheet.absoluteFill` (and 0.86 removed `absoluteFillObject`), so this one form
|
|
68
|
+
* type-checks on both. */
|
|
69
|
+
const FILL = { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 } as const;
|
|
70
|
+
|
|
66
71
|
const styles = StyleSheet.create({
|
|
67
72
|
overlay: {
|
|
68
|
-
...
|
|
73
|
+
...FILL,
|
|
69
74
|
alignItems: 'center',
|
|
70
75
|
justifyContent: 'center',
|
|
71
76
|
padding: DS_SPACING[800],
|
|
72
77
|
},
|
|
73
|
-
backdrop: { ...
|
|
78
|
+
backdrop: { ...FILL, backgroundColor: DS_SEMANTIC.element.overlayBackdrop },
|
|
74
79
|
card: {
|
|
75
80
|
width: '100%',
|
|
76
81
|
maxWidth: 400,
|