@scadable/wizard 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +92 -38
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -169,19 +169,36 @@ function parseArgs(argv) {
169
169
  return args;
170
170
  }
171
171
  var HELP = `
172
- ${pc.bold("@scadable/wizard")} - wire your SCADABLE privacy policy into this repo.
172
+ ${pc.bgCyan(pc.black(" SCADABLE "))} ${pc.bold("privacy wizard")}
173
+ Add a live privacy page to your app in about 20 seconds. Commit, deploy, done.
173
174
 
174
175
  ${pc.bold("Usage")}
175
176
  npx @scadable/wizard@latest --token <TEMP_TOKEN> [options]
176
177
 
178
+ ${pc.dim("Get your token from Settings in the SCADABLE app, then paste the whole command here.")}
179
+
177
180
  ${pc.bold("Options")}
178
181
  --token <token> Install token from the SCADABLE app (required).
179
182
  --api <url> API base. Default ${DEFAULT_API}.
180
- --dry-run Show the plan, write nothing.
181
- --yes, -y Skip confirmation prompts (non-interactive).
182
- --patch <file> Patch an existing file instead of creating a new page.
183
+ --dry-run Preview the plan without writing anything.
184
+ --yes, -y Skip the prompts (non-interactive).
185
+ --patch <file> Add the policy to an existing page instead of creating one.
183
186
  --help, -h Show this help.
187
+
188
+ ${pc.dim("Your code stays private: only package.json and folder names are read.")}
184
189
  `;
190
+ var BANNER = [
191
+ " _______. ______ ___ _______ ___ .______ __ _______ ",
192
+ " / | / | / \\ | \\ / \\ | _ \\ | | | ____|",
193
+ " | (----`| ,----' / ^ \\ | .--. | / ^ \\ | |_) | | | | |__ ",
194
+ " \\ \\ | | / /_\\ \\ | | | | / /_\\ \\ | _ < | | | __| ",
195
+ ".----) | | `----./ _____ \\ | '--' | / _____ \\ | |_) | | `----.| |____ ",
196
+ "|_______/ \\______/__/ \\__\\ |_______/ /__/ \\__\\ |______/ |_______||_______|"
197
+ ];
198
+ function printBanner() {
199
+ for (const line of BANNER) console.log(pc.cyan(line));
200
+ console.log("");
201
+ }
185
202
  function fail(message) {
186
203
  log.error(message);
187
204
  process.exit(1);
@@ -201,12 +218,12 @@ function showPlan(installCmd, edits, deterministic) {
201
218
  const blocks = [`${pc.bold("Install")}
202
219
  ${pc.cyan(installCmd)}`];
203
220
  for (const edit of edits) {
204
- const verb = edit.action === "patch" ? "Patch" : "Create";
221
+ const verb = edit.action === "patch" ? "Update" : "Create";
205
222
  blocks.push(`${pc.bold(`${verb} ${edit.path}`)}
206
223
  ${previewContents(edit.contents)}`);
207
224
  }
208
- const source = deterministic ? pc.dim("Plan is deterministic (template-based).") : pc.dim("Plan was generated for your repo.");
209
- note(blocks.join("\n\n") + "\n\n" + source, "Plan");
225
+ const source = deterministic ? pc.dim("Built from a vetted template. Same result every time.") : pc.dim("Tailored to your project.");
226
+ note(blocks.join("\n\n") + "\n\n" + source, "Here's the plan");
210
227
  }
211
228
  async function main() {
212
229
  const args = parseArgs(process.argv.slice(2));
@@ -214,56 +231,63 @@ async function main() {
214
231
  console.log(HELP);
215
232
  return;
216
233
  }
217
- intro(pc.bgCyan(pc.black(" SCADABLE privacy wizard ")));
234
+ printBanner();
235
+ intro(`${pc.bgCyan(pc.black(" SCADABLE "))} ${pc.bold("privacy wizard")}`);
236
+ log.message(pc.dim("Setting up your privacy page. About 20 seconds."));
218
237
  if (!args.token) {
219
- fail("Missing --token. Open Settings in the SCADABLE app and copy the install command.");
238
+ fail(
239
+ "No install token yet. Open Settings in SCADABLE, then copy and paste the whole install command."
240
+ );
220
241
  }
221
242
  const token = args.token;
222
243
  const api = args.api.replace(/\/$/, "");
223
244
  const cwd = process.cwd();
224
245
  const spin = spinner();
225
- spin.start("Verifying your install token");
246
+ spin.start("Checking your install token");
226
247
  let session;
227
248
  try {
228
249
  session = await getSession(api, token);
229
250
  } catch (err) {
230
- spin.stop("Token check failed");
251
+ spin.stop("Token check failed", 2);
231
252
  if (err instanceof WizardApiError && err.status === 401) {
232
253
  fail(
233
- "Your install token expired. Reopen Settings in the SCADABLE app and copy a fresh command."
254
+ "That install token has expired. Reopen Settings in SCADABLE and copy a fresh install command, then run it again."
234
255
  );
235
256
  }
236
257
  if (err instanceof WizardNetworkError) {
237
- fail(`Could not reach the SCADABLE API at ${api}. ${reason(err)}`);
258
+ fail(`Could not reach SCADABLE at ${api}. Check your connection and try again.`);
238
259
  }
239
260
  fail(`Could not verify your token: ${reason(err)}`);
240
261
  }
241
- spin.stop(`Connected to ${pc.bold(session.scope_name)} (${session.domain})`);
262
+ spin.stop(`Connected to ${pc.bold(session.scope_name)} ${pc.dim(`(${session.domain})`)}`);
263
+ spin.start("Reading your project");
242
264
  let ctx;
243
265
  try {
244
266
  ctx = detectRepo(cwd);
245
267
  } catch (err) {
268
+ spin.stop("Could not read your project", 2);
246
269
  fail(reason(err));
247
270
  }
271
+ spin.stop(`Found ${pc.bold(ctx.framework)}`);
272
+ log.message(pc.dim("Only package.json and folder names were read. Your code stays on your machine."));
248
273
  if (ctx.warning) log.warn(ctx.warning);
249
274
  let mode = "create";
250
275
  let target;
251
276
  if (args.patch) {
252
277
  const abs = resolve(cwd, args.patch);
253
278
  if (!existsSync3(abs)) {
254
- fail(`--patch file not found: ${args.patch}`);
279
+ fail(`Could not find the file to patch: ${args.patch}`);
255
280
  }
256
281
  const contents = readFileSync2(abs, "utf8");
257
282
  if (looksLikeSecret(contents)) {
258
283
  fail(
259
- `Refusing to upload ${args.patch}: it looks like it contains secrets. Drop --patch to create a fresh privacy page instead.`
284
+ `Not uploading ${args.patch}: it looks like it holds secrets. Drop --patch and the wizard will create a fresh privacy page instead.`
260
285
  );
261
286
  }
262
287
  mode = "patch";
263
288
  target = { path: args.patch, contents };
264
289
  }
265
- log.info(`Detected ${pc.bold(ctx.framework)} (mode: ${mode})`);
266
- spin.start("Building your install plan");
290
+ spin.start("Building your plan");
267
291
  let plan;
268
292
  try {
269
293
  plan = await postPlan(api, token, {
@@ -274,12 +298,12 @@ async function main() {
274
298
  target
275
299
  });
276
300
  } catch (err) {
277
- spin.stop("Could not build a plan");
301
+ spin.stop("Could not build a plan", 2);
278
302
  if (err instanceof WizardApiError && err.status === 503) {
279
- fail("SCADABLE could not generate a plan right now (model unavailable). Try again shortly.");
303
+ fail("SCADABLE is busy and could not build your plan right now. Give it a moment and try again.");
280
304
  }
281
305
  if (err instanceof WizardNetworkError) {
282
- fail(`Could not reach the SCADABLE API at ${api}. ${reason(err)}`);
306
+ fail(`Could not reach SCADABLE at ${api}. Check your connection and try again.`);
283
307
  }
284
308
  fail(`Could not build a plan: ${reason(err)}`);
285
309
  }
@@ -287,50 +311,80 @@ async function main() {
287
311
  const pm = detectPackageManager(cwd);
288
312
  const packages = resolvePackages(plan.install);
289
313
  const installCmd = formatInstall(pm, packages);
314
+ const liveUrl = `${api}/policy/${plan.public_id}`;
290
315
  showPlan(installCmd, plan.edits, plan.deterministic);
291
316
  if (args.dryRun) {
292
- outro(pc.dim("Dry run complete. Nothing was written."));
317
+ log.message(`${pc.dim("Your page would live at")} ${pc.cyan(liveUrl)}`);
318
+ outro(pc.dim("Dry run. Nothing was written. Run again without --dry-run to set it up."));
293
319
  return;
294
320
  }
295
321
  if (!args.yes) {
296
- const ok = await confirm({ message: "Apply this plan?" });
322
+ const ok = await confirm({
323
+ message: "Set up your privacy page?",
324
+ active: "Yes, do it",
325
+ inactive: "Not now",
326
+ initialValue: true
327
+ });
297
328
  if (isCancel(ok) || !ok) {
298
- cancel("No changes made.");
329
+ cancel("No problem. Nothing was changed. Run this again whenever you are ready.");
299
330
  process.exit(0);
300
331
  }
301
332
  }
302
- spin.start(`Installing with ${pm}`);
333
+ spin.start("Installing @scadable/privacy");
303
334
  try {
304
335
  runInstall(cwd, pm, packages);
305
- spin.stop("Dependency installed");
336
+ spin.stop(`Installed @scadable/privacy ${pc.dim(`(${pm})`)}`);
306
337
  } catch (err) {
307
- spin.stop("Install failed");
338
+ spin.stop("Install failed", 2);
308
339
  fail(`Could not run "${installCmd}": ${reason(err)}`);
309
340
  }
341
+ const toWrite = [];
310
342
  for (const edit of plan.edits) {
311
343
  const abs = resolve(cwd, edit.path);
312
344
  if (edit.action === "create" && existsSync3(abs) && !args.yes) {
313
- const overwrite = await confirm({ message: `${edit.path} already exists. Overwrite it?` });
345
+ const overwrite = await confirm({
346
+ message: `${edit.path} already exists. Replace it?`,
347
+ active: "Replace it",
348
+ inactive: "Keep mine",
349
+ initialValue: false
350
+ });
314
351
  if (isCancel(overwrite) || !overwrite) {
315
- log.warn(`Skipped ${edit.path}`);
352
+ log.warn(`Kept your existing ${edit.path}`);
316
353
  continue;
317
354
  }
318
355
  }
356
+ toWrite.push(edit);
357
+ }
358
+ spin.start("Writing your privacy page");
359
+ for (const edit of toWrite) {
319
360
  writeEdit(cwd, edit);
320
- log.success(`${edit.action === "patch" ? "Patched" : "Wrote"} ${edit.path}`);
321
361
  }
322
- spin.start("Finishing setup");
362
+ spin.stop(toWrite.length ? "Privacy page written" : "Nothing new to write");
363
+ spin.start("Finishing up");
323
364
  try {
324
365
  await postComplete(api, token);
325
- spin.stop("Marked connected");
366
+ spin.stop("Connected to SCADABLE");
326
367
  } catch (err) {
327
- spin.stop("Setup written, but could not notify SCADABLE");
328
- log.warn(`The files are in place, but marking the connection failed: ${reason(err)}`);
368
+ spin.stop("Files are in place, but could not reach SCADABLE to finish", 1);
369
+ log.warn(`Your files are written. Marking the connection failed: ${reason(err)}`);
329
370
  }
330
- outro(
331
- `${pc.green("Done.")} ${plan.route_hint}
332
- ${pc.dim("Public id:")} ${plan.public_id}`
333
- );
371
+ const payoff = [
372
+ `${pc.bold("Next:")} commit and deploy. That's it.`,
373
+ ` ${pc.cyan('git add -A && git commit -m "Add privacy policy"')}`,
374
+ ` ${pc.dim("then push, or run your usual deploy")}`,
375
+ ``,
376
+ `${pc.bold("Where it lives")}`,
377
+ ` ${pc.dim("Page ")} ${pc.cyan(plan.route_hint)}`,
378
+ ` ${pc.dim("Live ")} ${pc.cyan(liveUrl)}`,
379
+ ``,
380
+ pc.dim("It stays current. Update your policy in SCADABLE and every page"),
381
+ pc.dim("updates automatically. No redeploy."),
382
+ ``,
383
+ pc.dim("Using a Content-Security-Policy? Add api.scadable.com to connect-src"),
384
+ pc.dim("so the page can refresh live (it falls back to the saved copy if not).")
385
+ ].join("\n");
386
+ note(payoff, pc.green("Your privacy page is ready"));
387
+ outro(`${pc.green("Done.")} ${pc.dim("Commit and deploy, and you are live.")}`);
334
388
  }
335
389
  main().catch((err) => {
336
390
  log.error(reason(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scadable/wizard",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The SCADABLE setup wizard. Wires SCADABLE into your codebase (privacy policy now, more to come).",
5
5
  "license": "MIT",
6
6
  "type": "module",